mm/z3fold.c: Fix z3fold_destroy_pool() ordering
Subsystems (2)
PATCH 1/1 mm/z3fold.c: Fix z3fold_destroy_pool() ordering
The constraint from the zpool use of z3fold_destroy_pool() is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.
If there is work queued on pool->compact_workqueue when it is called, z3fold_destroy_pool() will do:
z3fold_destroy_pool()
destroy_workqueue(pool->release_wq)
destroy_workqueue(pool->compact_wq)
drain_workqueue(pool->compact_wq)
do_compact_page(zhdr)
kref_put(&zhdr->refcount)
__release_z3fold_page(zhdr, ...)
queue_work_on(pool->release_wq, &pool->work) *BOOM*So compact_wq needs to be destroyed before release_wq.
--- mm/z3fold.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
mm/z3fold.c +8 -1
@@ -818,8 +818,15 @@ static void z3fold_destroy_pool(struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle);z3fold_unregister_migration (pool); -destroy_workqueue (pool->release_wq); + +/* +* We need to destroy pool->compact_wq before pool->release_wq, +* as any pending work on pool->compact_wq will call +* queue_work(pool->release_wq, &pool->work). +*/ +destroy_workqueue (pool->compact_wq); +destroy_workqueue (pool->release_wq);kfree (pool); }
Signature · 1 line
2.22.0.709.g102302147b-goog
PATCH 1/1 mm/z3fold.c: Fix z3fold_destroy_pool() race condition
The constraint from the zpool use of z3fold_destroy_pool() is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.
Calling z3fold_deregister_migration() before the workqueues are drained means that there can be allocated pages referencing a freed inode, causing any thread in compaction to be able to trip over the bad pointer in PageMovable().
--- mm/z3fold.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/z3fold.c +4 -1
@@ -817,16 +817,19 @@ static struct z3fold_pool *z3fold_create_pool(const char *name, gfp_t gfp,static void z3fold_destroy_pool (struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle); -z3fold_unregister_migration (pool);/* * We need to destroy pool->compact_wq before pool->release_wq, * as any pending work on pool->compact_wq will call * queue_work(pool->release_wq, &pool->work). +* +* There are still outstanding pages until both workqueues are drained, +* so we cannot unregister migration until then. */ destroy_workqueue (pool->compact_wq);destroy_workqueue (pool->release_wq); +z3fold_unregister_migration (pool);kfree (pool); }
Signature · 1 line
2.22.0.709.g102302147b-goog
Re: [PATCH] mm/z3fold.c: Fix z3fold_destroy_pool() race condition
Quoted 5 lines · Henry Burns
The constraint from the zpool use of
z3fold_destroy_pool()is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.
Quoted 8 lines
Calling
z3fold_deregister_migration()before the workqueues are drained means that there can be allocated pages referencing a freed inode, causing any thread in compaction to be able to trip over the bad pointer inPageMovable().Fixes 1f862989b04a ("mm/z3fold.c: support page migration")Signed-off-by Henry Burns <[email protected]>
Quoted 33 lines
Cc <[email protected]>--- mm/z3fold.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/z3fold.c +4
-2@@ -817,16 +817,19 @@ static struct z3fold_pool *z3fold_create_pool(const char *name, gfp_t gfp,static void z3fold_destroy_pool (struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle); -z3fold_unregister_migration (pool);/* * We need to destroy pool->compact_wq before pool->release_wq, * as any pending work on pool->compact_wq will call * queue_work(pool->release_wq, &pool->work). +* +* There are still outstanding pages until both workqueues are drained, +* so we cannot unregister migration until then. */ destroy_workqueue (pool->compact_wq);destroy_workqueue (pool->release_wq); +z3fold_unregister_migration (pool);kfree (pool); } --2.22.0.709.g102302147b-goog
Re: [PATCH] mm/z3fold.c: Fix z3fold_destroy_pool() race condition
In reply to Henry BurnsQuoted 5 lines · Henry Burns
The constraint from the zpool use of
z3fold_destroy_pool()is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.
Quoted 8 lines
Calling
z3fold_deregister_migration()before the workqueues are drained means that there can be allocated pages referencing a freed inode, causing any thread in compaction to be able to trip over the bad pointer inPageMovable().Fixes 1f862989b04a ("mm/z3fold.c: support page migration")Signed-off-by Henry Burns <[email protected]>
Quoted 33 lines
Cc <[email protected]>--- mm/z3fold.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/z3fold.c +4
-2@@ -817,16 +817,19 @@ static struct z3fold_pool *z3fold_create_pool(const char *name, gfp_t gfp,static void z3fold_destroy_pool (struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle); -z3fold_unregister_migration (pool);/* * We need to destroy pool->compact_wq before pool->release_wq, * as any pending work on pool->compact_wq will call * queue_work(pool->release_wq, &pool->work). +* +* There are still outstanding pages until both workqueues are drained, +* so we cannot unregister migration until then. */ destroy_workqueue (pool->compact_wq);destroy_workqueue (pool->release_wq); +z3fold_unregister_migration (pool);kfree (pool); } --2.22.0.709.g102302147b-goog
Re: [PATCH] mm/z3fold.c: Fix z3fold_destroy_pool() race condition
The constraint from the zpool use of z3fold_destroy_pool() is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.
Calling z3fold_deregister_migration() before the workqueues are drained means that there can be allocated pages referencing a freed inode, causing any thread in compaction to be able to trip over the bad pointer in PageMovable().
Quoted 36 lines
Reviewed-by Shakeel Butt <[email protected]>Reviewed-by Jonathan Adams <[email protected]>Quoted 33 lines
Cc <[email protected]>--- mm/z3fold.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/z3fold.c +4
-2@@ -817,16 +817,19 @@ static struct z3fold_pool *z3fold_create_pool(const char *name, gfp_t gfp,static void z3fold_destroy_pool (struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle); -z3fold_unregister_migration (pool);/* * We need to destroy pool->compact_wq before pool->release_wq, * as any pending work on pool->compact_wq will call * queue_work(pool->release_wq, &pool->work). +* +* There are still outstanding pages until both workqueues are drained, +* so we cannot unregister migration until then. */ destroy_workqueue (pool->compact_wq);destroy_workqueue (pool->release_wq); +z3fold_unregister_migration (pool);kfree (pool); } --2.22.0.709.g102302147b-goog
Quoted 17 lines · Henry Burns
The constraint from the zpool use of
z3fold_destroy_pool()is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.If there is work queued on
pool->compact_workqueuewhen it is called,z3fold_destroy_pool()will do:z3fold_destroy_pool() destroy_workqueue(pool->release_wq) destroy_workqueue(pool->compact_wq) drain_workqueue(pool->compact_wq) do_compact_page(zhdr) kref_put(&zhdr->refcount) __release_z3fold_page(zhdr, ...) queue_work_on(pool->release_wq, &pool->work) *BOOM*
Quoted 5 lines
So
compact_wqneeds to be destroyed beforerelease_wq.Fixes 5d03a6613957 ("mm/z3fold.c: use kref to prevent page free/compact race")Signed-off-by Henry Burns <[email protected]>
Quoted 29 lines
Cc <[email protected]>--- mm/z3fold.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)mm/z3fold.c +8
-2@@ -818,8 +818,15 @@ static void z3fold_destroy_pool(struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle);z3fold_unregister_migration (pool); -destroy_workqueue (pool->release_wq); + +/* +* We need to destroy pool->compact_wq before pool->release_wq, +* as any pending work on pool->compact_wq will call +* queue_work(pool->release_wq, &pool->work). +*/ +destroy_workqueue (pool->compact_wq); +destroy_workqueue (pool->release_wq);kfree (pool); } --2.22.0.709.g102302147b-goog
Quoted 17 lines · Henry Burns
The constraint from the zpool use of
z3fold_destroy_pool()is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.If there is work queued on
pool->compact_workqueuewhen it is called,z3fold_destroy_pool()will do:z3fold_destroy_pool() destroy_workqueue(pool->release_wq) destroy_workqueue(pool->compact_wq) drain_workqueue(pool->compact_wq) do_compact_page(zhdr) kref_put(&zhdr->refcount) __release_z3fold_page(zhdr, ...) queue_work_on(pool->release_wq, &pool->work) *BOOM*
Quoted 5 lines
So
compact_wqneeds to be destroyed beforerelease_wq.Fixes 5d03a6613957 ("mm/z3fold.c: use kref to prevent page free/compact race")Signed-off-by Henry Burns <[email protected]>
Quoted 29 lines
Cc <[email protected]>--- mm/z3fold.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)mm/z3fold.c +8
-2@@ -818,8 +818,15 @@ static void z3fold_destroy_pool(struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle);z3fold_unregister_migration (pool); -destroy_workqueue (pool->release_wq); + +/* +* We need to destroy pool->compact_wq before pool->release_wq, +* as any pending work on pool->compact_wq will call +* queue_work(pool->release_wq, &pool->work). +*/ +destroy_workqueue (pool->compact_wq); +destroy_workqueue (pool->release_wq);kfree (pool); } --2.22.0.709.g102302147b-goog
The constraint from the zpool use of z3fold_destroy_pool() is there are no outstanding handles to memory (so no active allocations), but it is possible for there to be outstanding work on either of the two wqs in the pool.
If there is work queued on pool->compact_workqueue when it is called, z3fold_destroy_pool() will do:
z3fold_destroy_pool()
destroy_workqueue(pool->release_wq)
destroy_workqueue(pool->compact_wq)
drain_workqueue(pool->compact_wq)
do_compact_page(zhdr)
kref_put(&zhdr->refcount)
__release_z3fold_page(zhdr, ...)
queue_work_on(pool->release_wq, &pool->work) *BOOM*So compact_wq needs to be destroyed before release_wq.
Quoted 32 lines
Reviewed-by Shakeel Butt <[email protected]>Reviewed-by Jonathan Adams <[email protected]>Quoted 29 lines
Cc <[email protected]>--- mm/z3fold.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)mm/z3fold.c +8
-2@@ -818,8 +818,15 @@ static void z3fold_destroy_pool(struct z3fold_pool *pool) {kmem_cache_destroy (pool->c_handle);z3fold_unregister_migration (pool); -destroy_workqueue (pool->release_wq); + +/* +* We need to destroy pool->compact_wq before pool->release_wq, +* as any pending work on pool->compact_wq will call +* queue_work(pool->release_wq, &pool->work). +*/ +destroy_workqueue (pool->compact_wq); +destroy_workqueue (pool->release_wq);kfree (pool); } --2.22.0.709.g102302147b-goog