It is possible to trigger use after free during HPT resize
causing host kernel to crash. More details and analysis of
the problem can be found in change with corresponding subject
(KVM: PPC: Book3S HV: Fix use after free in case of multiple
resize requests).
We need some changes to prepare for the fix, especially
make ->error in HPT resize instance single point for
tracking allocation state, improve kvmppc_allocate_hpt()
and kvmppc_free_hpt() so they can be used more safely.
See individual commit description message to get more
information on changes presented.
Serhii Popovych (4):
KVM: PPC: Book3S HV: Drop prepare_done from struct kvm_resize_hpt and
cleanups
KVM: PPC: Book3S HV: Improve kvmppc_allocate_hpt()/kvmppc_free_hpt()
KVM: PPC: Book3S HV: Fix use after free in case of multiple resize
requests
KVM: PPC: Book3S HV: Remove redundant parameter from
resize_hpt_release()
arch/powerpc/kvm/book3s_64_mmu_hv.c | 139 +++++++++++++++++++++---------------
1 file changed, 82 insertions(+), 57 deletions(-)
--
1.8.3.1
Replace ->prepare_done flag functionality with special handling
of -EBUSY in ->error as indicator that allocation work is running.
Besides cosmetics this reduces size of struct kvm_resize_hpt by
__alignof__(struct kvm_hpt_info) and saves few bytes of code.
While there correct comment in struct kvm_resize_hpt about locking
used to protect access to certain fields.
Assert with BUG_ON() in case of HPT allocation thread work runs
more than once for resize request or resize_hpt_allocate()
returns -EBUSY that is treated specially.
Change comparison against zero to make checkpatch.pl happy.
Signed-off-by: Serhii Popovych <redacted>
---
arch/powerpc/kvm/book3s_64_mmu_hv.c | 42 ++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 17 deletions(-)
@@ -65,11 +65,17 @@ struct kvm_resize_hpt {u32order;/* These fields protected by kvm->lock */++/* Possible values and their usage:+*<0anerroroccurredduringallocation,+*-EBUSYallocationisintheprogress,+*0allocationmadesuccessfuly.+*/interror;-boolprepare_done;-/* Private to the work thread, until prepare_done is true,-*thenprotectedbykvm->resize_hpt_sem*/+/* Private to the work thread, until error != -EBUSY,+*thenprotectedbykvm->lock.+*/structkvm_hpt_infohpt;};
@@ -1432,15 +1438,21 @@ static void resize_hpt_prepare_work(struct work_struct *work)structkvm*kvm=resize->kvm;interr;+BUG_ON(resize->error!=-EBUSY);+resize_hpt_debug(resize,"resize_hpt_prepare_work(): order = %d\n",resize->order);err=resize_hpt_allocate(resize);+/* We have strict assumption about -EBUSY+*whenpreparingforHPTresize.+*/+BUG_ON(err==-EBUSY);+mutex_lock(&kvm->lock);resize->error=err;-resize->prepare_done=true;mutex_unlock(&kvm->lock);}
@@ -1465,14 +1477,12 @@ long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,if(resize){if(resize->order==shift){-/* Suitable resize in progress */-if(resize->prepare_done){-ret=resize->error;-if(ret!=0)-resize_hpt_release(kvm,resize);-}else{+/* Suitable resize in progress? */+ret=resize->error;+if(ret==-EBUSY)ret=100;/* estimated time in ms */-}+elseif(ret)+resize_hpt_release(kvm,resize);gotoout;}
@@ -1492,6 +1502,8 @@ long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,ret=-ENOMEM;gotoout;}++resize->error=-EBUSY;resize->order=shift;resize->kvm=kvm;INIT_WORK(&resize->work,resize_hpt_prepare_work);
@@ -1546,16 +1558,12 @@ long kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm,if(!resize||(resize->order!=shift))gotoout;-ret=-EBUSY;-if(!resize->prepare_done)-gotoout;-ret=resize->error;-if(ret!=0)+if(ret)gotoout;ret=resize_hpt_rehash(resize);-if(ret!=0)+if(ret)gotoout;resize_hpt_pivot(resize);
There is no need to pass it explicitly from the caller:
struct kvm_resize_hpt already contains it.
Additional benefit from this change is that BUG_ON()
assertion now checks that mutex is held on kvm instance
associated with resize structure we going to release.
Also kill check for resize being NULL to make code
simpler and we called with resize != NULL in all
places except kvm_vm_ioctl_resize_hpt_commit().
Signed-off-by: Serhii Popovych <redacted>
---
arch/powerpc/kvm/book3s_64_mmu_hv.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
@@ -1499,13 +1498,13 @@ long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,if(ret==-EBUSY)ret=100;/* estimated time in ms */elseif(ret)-resize_hpt_release(kvm,resize);+resize_hpt_release(resize);gotoout;}/* not suitable, cancel it */-resize_hpt_release(kvm,resize);+resize_hpt_release(resize);}ret=0;
@@ -1590,7 +1589,8 @@ long kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm,kvm->arch.mmu_ready=1;smp_mb();out_no_hpt:-resize_hpt_release(kvm,resize);+if(resize)+resize_hpt_release(resize);mutex_unlock(&kvm->lock);returnret;}
@@ -1434,24 +1437,40 @@ static void resize_hpt_prepare_work(struct work_struct *work)structkvm_resize_hpt,work);structkvm*kvm=resize->kvm;-interr;+interr=0;BUG_ON(resize->error!=-EBUSY);-resize_hpt_debug(resize,"resize_hpt_prepare_work(): order = %d\n",-resize->order);+mutex_lock(&kvm->lock);++/* Request is still current? */+if(kvm->arch.resize_hpt==resize){+/* We may request large allocations here:+*donotsleepwithkvm->lockheldforawhile.+*/+mutex_unlock(&kvm->lock);-err=resize_hpt_allocate(resize);+resize_hpt_debug(resize,"resize_hpt_prepare_work(): order = %d\n",+resize->order);-/* We have strict assumption about -EBUSY-*whenpreparingforHPTresize.-*/-BUG_ON(err==-EBUSY);+err=resize_hpt_allocate(resize);-mutex_lock(&kvm->lock);+/* We have strict assumption about -EBUSY+*whenpreparingforHPTresize.+*/+BUG_ON(err==-EBUSY);++mutex_lock(&kvm->lock);+/* It is possible that kvm->arch.resize_hpt != resize+*afterwegrabkvm->lockagain.+*/+}resize->error=err;+if(kvm->arch.resize_hpt!=resize)+resize_hpt_release(kvm,resize);+mutex_unlock(&kvm->lock);}
There are several points of improvements:
1) Make kvmppc_free_hpt() check if allocation is made before attempt
to release. This follows kfree(p) semantics where p == NULL.
2) Return initialized @info parameter from kvmppc_allocate_hpt()
even if allocation fails.
This allows to use kvmppc_free_hpt() in the caller without
checking that preceded kvmppc_allocate_hpt() was successful
p = kmalloc(size, gfp);
kfree(p);
which is correct for both p != NULL and p == NULL. Followup
change will rely on this behaviour.
3) Better code reuse: kvmppc_free_hpt() can be reused on error
path in kvmppc_allocate_hpt() to avoid code duplication.
4) No need to check for !hpt if allocated from CMA: neither
pfn_to_kaddr() nor page_to_pfn() is 0 in case of page != NULL.
Signed-off-by: Serhii Popovych <redacted>
---
arch/powerpc/kvm/book3s_64_mmu_hv.c | 54 ++++++++++++++++++-------------------
1 file changed, 26 insertions(+), 28 deletions(-)
From: David Gibson <hidden> Date: 2017-11-30 04:03:05
On Wed, Nov 29, 2017 at 11:38:22AM -0500, Serhii Popovych wrote:
It is possible to trigger use after free during HPT resize
causing host kernel to crash. More details and analysis of
the problem can be found in change with corresponding subject
(KVM: PPC: Book3S HV: Fix use after free in case of multiple
resize requests).
We need some changes to prepare for the fix, especially
make ->error in HPT resize instance single point for
tracking allocation state, improve kvmppc_allocate_hpt()
and kvmppc_free_hpt() so they can be used more safely.
See individual commit description message to get more
information on changes presented.
Serhii Popovych (4):
KVM: PPC: Book3S HV: Drop prepare_done from struct kvm_resize_hpt and
cleanups
KVM: PPC: Book3S HV: Improve kvmppc_allocate_hpt()/kvmppc_free_hpt()
KVM: PPC: Book3S HV: Fix use after free in case of multiple resize
requests
KVM: PPC: Book3S HV: Remove redundant parameter from
resize_hpt_release()
arch/powerpc/kvm/book3s_64_mmu_hv.c | 139 +++++++++++++++++++++---------------
1 file changed, 82 insertions(+), 57 deletions(-)
Paul, these (at least 1-3) fix (another :() host crash bug which can
be triggered by guest and/or userspace actions. Please merge ASAP.
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
From: David Gibson <hidden> Date: 2017-11-30 04:03:06
On Wed, Nov 29, 2017 at 11:38:24AM -0500, Serhii Popovych wrote:
There are several points of improvements:
1) Make kvmppc_free_hpt() check if allocation is made before attempt
to release. This follows kfree(p) semantics where p == NULL.
2) Return initialized @info parameter from kvmppc_allocate_hpt()
even if allocation fails.
This allows to use kvmppc_free_hpt() in the caller without
checking that preceded kvmppc_allocate_hpt() was successful
p = kmalloc(size, gfp);
kfree(p);
which is correct for both p != NULL and p == NULL. Followup
change will rely on this behaviour.
3) Better code reuse: kvmppc_free_hpt() can be reused on error
path in kvmppc_allocate_hpt() to avoid code duplication.
4) No need to check for !hpt if allocated from CMA: neither
pfn_to_kaddr() nor page_to_pfn() is 0 in case of page != NULL.
Signed-off-by: Serhii Popovych <redacted>
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
From: David Gibson <hidden> Date: 2017-11-30 04:03:08
On Wed, Nov 29, 2017 at 11:38:23AM -0500, Serhii Popovych wrote:
Replace ->prepare_done flag functionality with special handling
of -EBUSY in ->error as indicator that allocation work is running.
Besides cosmetics this reduces size of struct kvm_resize_hpt by
__alignof__(struct kvm_hpt_info) and saves few bytes of code.
While there correct comment in struct kvm_resize_hpt about locking
used to protect access to certain fields.
Assert with BUG_ON() in case of HPT allocation thread work runs
more than once for resize request or resize_hpt_allocate()
returns -EBUSY that is treated specially.
Change comparison against zero to make checkpatch.pl happy.
Signed-off-by: Serhii Popovych <redacted>
@@ -65,11 +65,17 @@ struct kvm_resize_hpt {u32order;/* These fields protected by kvm->lock */++/* Possible values and their usage:+*<0anerroroccurredduringallocation,+*-EBUSYallocationisintheprogress,+*0allocationmadesuccessfuly.+*/interror;-boolprepare_done;-/* Private to the work thread, until prepare_done is true,-*thenprotectedbykvm->resize_hpt_sem*/+/* Private to the work thread, until error != -EBUSY,+*thenprotectedbykvm->lock.+*/structkvm_hpt_infohpt;};
@@ -1432,15 +1438,21 @@ static void resize_hpt_prepare_work(struct work_struct *work)structkvm*kvm=resize->kvm;interr;+BUG_ON(resize->error!=-EBUSY);+resize_hpt_debug(resize,"resize_hpt_prepare_work(): order = %d\n",resize->order);err=resize_hpt_allocate(resize);+/* We have strict assumption about -EBUSY+*whenpreparingforHPTresize.+*/+BUG_ON(err==-EBUSY);+mutex_lock(&kvm->lock);resize->error=err;-resize->prepare_done=true;mutex_unlock(&kvm->lock);}
@@ -1465,14 +1477,12 @@ long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,if(resize){if(resize->order==shift){-/* Suitable resize in progress */-if(resize->prepare_done){-ret=resize->error;-if(ret!=0)-resize_hpt_release(kvm,resize);-}else{+/* Suitable resize in progress? */+ret=resize->error;+if(ret==-EBUSY)ret=100;/* estimated time in ms */-}+elseif(ret)+resize_hpt_release(kvm,resize);gotoout;}
@@ -1492,6 +1502,8 @@ long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,ret=-ENOMEM;gotoout;}++resize->error=-EBUSY;resize->order=shift;resize->kvm=kvm;INIT_WORK(&resize->work,resize_hpt_prepare_work);
@@ -1546,16 +1558,12 @@ long kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm,if(!resize||(resize->order!=shift))gotoout;-ret=-EBUSY;-if(!resize->prepare_done)-gotoout;-ret=resize->error;-if(ret!=0)+if(ret)gotoout;ret=resize_hpt_rehash(resize);-if(ret!=0)+if(ret)gotoout;resize_hpt_pivot(resize);
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
From: David Gibson <hidden> Date: 2017-11-30 04:03:49
On Wed, Nov 29, 2017 at 11:38:26AM -0500, Serhii Popovych wrote:
There is no need to pass it explicitly from the caller:
struct kvm_resize_hpt already contains it.
Additional benefit from this change is that BUG_ON()
assertion now checks that mutex is held on kvm instance
associated with resize structure we going to release.
Also kill check for resize being NULL to make code
simpler and we called with resize != NULL in all
places except kvm_vm_ioctl_resize_hpt_commit().
Signed-off-by: Serhii Popovych <redacted>
@@ -1499,13 +1498,13 @@ long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,if(ret==-EBUSY)ret=100;/* estimated time in ms */elseif(ret)-resize_hpt_release(kvm,resize);+resize_hpt_release(resize);gotoout;}/* not suitable, cancel it */-resize_hpt_release(kvm,resize);+resize_hpt_release(resize);}ret=0;
@@ -1590,7 +1589,8 @@ long kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm,kvm->arch.mmu_ready=1;smp_mb();out_no_hpt:-resize_hpt_release(kvm,resize);+if(resize)+resize_hpt_release(resize);mutex_unlock(&kvm->lock);returnret;}
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
@@ -1434,24 +1437,40 @@ static void resize_hpt_prepare_work(struct work_struct *work)structkvm_resize_hpt,work);structkvm*kvm=resize->kvm;-interr;+interr=0;BUG_ON(resize->error!=-EBUSY);-resize_hpt_debug(resize,"resize_hpt_prepare_work(): order = %d\n",-resize->order);+mutex_lock(&kvm->lock);++/* Request is still current? */+if(kvm->arch.resize_hpt==resize){+/* We may request large allocations here:+*donotsleepwithkvm->lockheldforawhile.+*/+mutex_unlock(&kvm->lock);-err=resize_hpt_allocate(resize);+resize_hpt_debug(resize,"resize_hpt_prepare_work(): order = %d\n",+resize->order);-/* We have strict assumption about -EBUSY-*whenpreparingforHPTresize.-*/-BUG_ON(err==-EBUSY);+err=resize_hpt_allocate(resize);-mutex_lock(&kvm->lock);+/* We have strict assumption about -EBUSY+*whenpreparingforHPTresize.+*/+BUG_ON(err==-EBUSY);++mutex_lock(&kvm->lock);+/* It is possible that kvm->arch.resize_hpt != resize+*afterwegrabkvm->lockagain.+*/+}resize->error=err;+if(kvm->arch.resize_hpt!=resize)+resize_hpt_release(kvm,resize);+mutex_unlock(&kvm->lock);}
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
From: David Gibson <hidden> Date: 2017-12-04 06:12:26
On Wed, Nov 29, 2017 at 11:38:22AM -0500, Serhii Popovych wrote:
It is possible to trigger use after free during HPT resize
causing host kernel to crash. More details and analysis of
the problem can be found in change with corresponding subject
(KVM: PPC: Book3S HV: Fix use after free in case of multiple
resize requests).
We need some changes to prepare for the fix, especially
make ->error in HPT resize instance single point for
tracking allocation state, improve kvmppc_allocate_hpt()
and kvmppc_free_hpt() so they can be used more safely.
See individual commit description message to get more
information on changes presented.
I spoke with Paul Mackerras about these patches on IRC today. We want
this as a fix, ASAP, in 4.15. However, he's uncomfortable with
pushing some of extra cleanups which aren't necessary for the bug fix
this late for 4.15, and was having trouble following what was the core
of the fix. He was also nervous about the addition of more BUG_ON()s.
To avoid the round trip to Ukraine time and back, I've made revised
versions of patches 1 & 3 which should apply standalone, replaced the
BUG_ON()s with WARN_ON()s and revised the commit messages to better
explain the crucial part of the fix.
However, I've run out of time to test them.
Serhii, I'll send you my revised patches shortly. Can you please
test them and repost. Then you can rebase patches 2 & 4 from this
series on top of the revised patches and post those separately (as a
cleanup with less urgency than the actual fix).
A couple of people have also suggested CCing kvm@vger.kernel.org on
the next round in addition to the lists already included.
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
From: Michael Ellerman <hidden> Date: 2017-12-04 12:22:53
David Gibson [off-list ref] writes:
On Wed, Nov 29, 2017 at 11:38:22AM -0500, Serhii Popovych wrote:
quoted
It is possible to trigger use after free during HPT resize
causing host kernel to crash. More details and analysis of
the problem can be found in change with corresponding subject
(KVM: PPC: Book3S HV: Fix use after free in case of multiple
resize requests).
We need some changes to prepare for the fix, especially
make ->error in HPT resize instance single point for
tracking allocation state, improve kvmppc_allocate_hpt()
and kvmppc_free_hpt() so they can be used more safely.
See individual commit description message to get more
information on changes presented.
I spoke with Paul Mackerras about these patches on IRC today. We want
this as a fix, ASAP, in 4.15. However, he's uncomfortable with
pushing some of extra cleanups which aren't necessary for the bug fix
this late for 4.15, and was having trouble following what was the core
of the fix. He was also nervous about the addition of more BUG_ON()s.
As was I.
To avoid the round trip to Ukraine time and back, I've made revised
versions of patches 1 & 3 which should apply standalone, replaced the
BUG_ON()s with WARN_ON()s
On Wed, Nov 29, 2017 at 11:38:22AM -0500, Serhii Popovych wrote:
quoted
It is possible to trigger use after free during HPT resize
causing host kernel to crash. More details and analysis of
the problem can be found in change with corresponding subject
(KVM: PPC: Book3S HV: Fix use after free in case of multiple
resize requests).
We need some changes to prepare for the fix, especially
make ->error in HPT resize instance single point for
tracking allocation state, improve kvmppc_allocate_hpt()
and kvmppc_free_hpt() so they can be used more safely.
See individual commit description message to get more
information on changes presented.
I spoke with Paul Mackerras about these patches on IRC today. We want
this as a fix, ASAP, in 4.15. However, he's uncomfortable with
pushing some of extra cleanups which aren't necessary for the bug fix
this late for 4.15, and was having trouble following what was the core
of the fix. He was also nervous about the addition of more BUG_ON()s.
Good, no problem, cleanups will be pushed additionally.
To avoid the round trip to Ukraine time and back, I've made revised
versions of patches 1 & 3 which should apply standalone, replaced the
BUG_ON()s with WARN_ON()s and revised the commit messages to better
explain the crucial part of the fix.
However, I've run out of time to test them.
I did the same test as for this v1 series and found no problem with v2
you sent to me: it seems patch improving kvmppc_allocate_hpt() and
kvmppc_free_hpt() isn't actually necessary as I was thinking when
submitting v1.
Serhii, I'll send you my revised patches shortly. Can you please
test them and repost. Then you can rebase patches 2 & 4 from this
series on top of the revised patches and post those separately (as a
cleanup with less urgency than the actual fix).
Tested with same test case as with v1: no problem so far.
A couple of people have also suggested CCing kvm@vger.kernel.org on
the next round in addition to the lists already included.