548df8b4b57b (ve/userns: associate user_struct with the user_namespace, 
2017-03-13)
introduced dynamically allocated per-userns uid hastable
instead of using a global static hash table.

The problem with that allocate hashtable is that life cycle of
the two objects is different - both structes use reference
counts but they are counted separately. The contained objects
(user_struct) are not referenced from user_namespace.
The result of that is that on ve destroy last reference to
user_namespace is dropped and it is freed - the allocated
hash table is freed too.

But user_structs that were hashed can still be used elsewhere
(like on inodes), so when the reference to that structs is dropped
(evict_inodes) they try to remove from the hash which was alrady freed.
This results in writting a NULL to an already freed memory.
But since this happens after some time that write usually corrupts
some new allocation which results in a variety of crashes.

To fix this remove all user_structs that are still in the hash
when destroing user_namespace. The hash is used to lookup them
but since the user_namespace is gone no further lookups are possible.

https://jira.vzint.dev/browse/PSBM-150648
Signed-off-by: Alexander Atanasov <alexander.atana...@virtuozzo.com>
---
 include/linux/sched.h   |  1 +
 kernel/user.c           | 13 +++++++++++++
 kernel/user_namespace.c |  4 ++++
 3 files changed, 18 insertions(+)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index f49ea922e61a..772d5d500c1f 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -830,6 +830,7 @@ struct user_struct {
 extern int uids_sysfs_init(void);
 
 extern struct user_struct *find_user(kuid_t);
+extern void uid_hash_remove_all(struct hlist_head *hlist);
 
 extern struct user_struct root_user;
 #define INIT_USER (&root_user)
diff --git a/kernel/user.c b/kernel/user.c
index 62c7d79b8cbf..de59631d163b 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -107,6 +107,19 @@ static void uid_hash_remove(struct user_struct *up)
        hlist_del_init(&up->uidhash_node);
 }
 
+void uid_hash_remove_all(struct hlist_head *hlist)
+{
+       struct user_struct *up;
+       const struct hlist_node *tmp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&uidhash_lock, flags);
+       if (!hlist_empty(hlist))
+               hlist_for_each_entry_safe(up, tmp, hlist, uidhash_node)
+                       hlist_del_init(&up->uidhash_node);
+       spin_unlock_irqrestore(&uidhash_lock, flags);
+}
+
 static struct user_struct *uid_hash_find(kuid_t uid, struct hlist_head 
*hashent)
 {
        struct user_struct *user;
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index f7bd1e66e752..1759671e62ad 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -203,6 +203,7 @@ static void free_user_ns(struct work_struct *work)
 {
        struct user_namespace *parent, *ns =
                container_of(work, struct user_namespace, work);
+       int i;
 
        do {
                struct ucounts *ucounts = ns->ucounts;
@@ -212,6 +213,9 @@ static void free_user_ns(struct work_struct *work)
                key_put(ns->persistent_keyring_register);
 #endif
                ns_free_inum(&ns->ns);
+               for (i = 0; i < UIDHASH_SZ; ++i)
+                       uid_hash_remove_all(ns->uidhash_table + i);
+
                kmem_cache_free(user_ns_cachep, ns);
                dec_user_namespaces(ucounts);
                ns = parent;
-- 
2.39.3

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to