From 009afa1dc45e8007200fc7dc2b200d0b89b85932 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 9 Feb 2017 10:09:43 -0800 Subject: [PATCH] kernel - Fix vmspace termination race * Fix a SMP race which can cause a vmspace structure to be double-freed to its objcache, resulting in corruption. * Typical panic was a kernel seg fault at 'vm_copyin+2'. What was in fact happening was that corruption of the vmspace was racing against reuse and a decrement of the pmap copyin function pointer. This decrement caused function calls through the pointer to get very confused. Reported-by: tuxillo --- sys/vm/vm_map.c | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/sys/vm/vm_map.c b/sys/vm/vm_map.c index 07ffd4c9f7..ffc4fa6eaa 100644 --- a/sys/vm/vm_map.c +++ b/sys/vm/vm_map.c @@ -256,7 +256,7 @@ void vmspace_initrefs(struct vmspace *vm) { vm->vm_refcnt = 1; - vm->vm_holdcnt = 0; + vm->vm_holdcnt = 1; } /* @@ -308,7 +308,13 @@ vmspace_alloc(vm_offset_t min, vm_offset_t max) int vmspace_getrefs(struct vmspace *vm) { - return ((int)(vm->vm_refcnt & ~VM_REF_DELETED)); + int32_t n; + + n = vm->vm_refcnt; + cpu_ccfence(); + if (n & VM_REF_DELETED) + n = -1; + return n; } void @@ -318,6 +324,9 @@ vmspace_hold(struct vmspace *vm) lwkt_gettoken(&vm->vm_map.token); } +/* + * Drop with final termination interlock. + */ void vmspace_drop(struct vmspace *vm) { @@ -349,6 +358,7 @@ vmspace_ref(struct vmspace *vm) { uint32_t n; + atomic_add_int(&vm->vm_holdcnt, 1); n = atomic_fetchadd_int(&vm->vm_refcnt, 1); KKASSERT((n & VM_REF_DELETED) == 0); } @@ -363,28 +373,23 @@ vmspace_rel(struct vmspace *vm) { uint32_t n; - for (;;) { + /* + * Drop refs. Each ref also has a hold which is also dropped. + * + * When refs hits 0 compete to get the VM_REF_DELETED flag (hold + * prevent finalization) to start termination processing. + * Finalization occurs when the last hold count drops to 0. + */ + n = atomic_fetchadd_int(&vm->vm_refcnt, -1) - 1; + while (n == 0) { + if (atomic_cmpset_int(&vm->vm_refcnt, 0, VM_REF_DELETED)) { + vmspace_terminate(vm, 0); + break; + } n = vm->vm_refcnt; cpu_ccfence(); - KKASSERT((int)n > 0); /* at least one ref & not deleted */ - - if (n == 1) { - /* - * We must have a hold first to interlock the - * VM_REF_DELETED check that the drop tests. - */ - atomic_add_int(&vm->vm_holdcnt, 1); - if (atomic_cmpset_int(&vm->vm_refcnt, n, - VM_REF_DELETED)) { - vmspace_terminate(vm, 0); - vmspace_drop_notoken(vm); - break; - } - vmspace_drop_notoken(vm); - } else if (atomic_cmpset_int(&vm->vm_refcnt, n, n - 1)) { - break; - } /* else retry */ } + vmspace_drop_notoken(vm); } /* -- 2.11.4.GIT