diff --git a/security/lua/kvcache.c b/security/lua/kvcache.c index 89c4627fadaf..9f82885ab145 100644 --- a/security/lua/kvcache.c +++ b/security/lua/kvcache.c @@ -477,6 +477,30 @@ void kvcache_dict_init(struct kvcache_dict *dict) dict->capacity = CACHE_CAPACITY; } +static void lua_lsm_shdict_free_rcu(struct rcu_head *head) +{ + struct lua_lsm_module_shdict *shdict; + + shdict = container_of(head, struct lua_lsm_module_shdict, rcu); + kvcache_dict_free(&shdict->dict); + kfree(shdict); +} + +void lua_lsm_shdict_get(struct lua_lsm_module_shdict *shdict) +{ + if (shdict) + refcount_acquire(&shdict->refcount); +} + +void lua_lsm_shdict_put(struct lua_lsm_module_shdict *shdict) +{ + if (!shdict) + return; + + if (refcount_release(&shdict->refcount) == 0) + call_rcu(&shdict->rcu, lua_lsm_shdict_free_rcu); +} + /******************************** object cache *******************************/ const int _module_sentinel; @@ -595,28 +619,37 @@ int lua_object_newindex(lua_State *L, struct kvcache_dict *dict) static int shdict_set(lua_State *L) { - struct kvcache_dict *shdict = toshdict(L, 1); + struct lua_lsm_module_shdict *shdict = toshdict(L, 1); - return kvcache_set(L, shdict, NULL); + if (READ_ONCE(shdict->dead)) + return kvcache_result(L, -ESRCH); + + return kvcache_set(L, &shdict->dict, NULL); } static int shdict_get(lua_State *L) { - struct kvcache_dict *shdict = toshdict(L, 1); + struct lua_lsm_module_shdict *shdict = toshdict(L, 1); + + if (READ_ONCE(shdict->dead)) + return kvcache_result(L, -ESRCH); - return kvcache_get(L, shdict, NULL); + return kvcache_get(L, &shdict->dict, NULL); } static int shdict_incr(lua_State *L) { - struct kvcache_dict *shdict = toshdict(L, 1); + struct lua_lsm_module_shdict *shdict = toshdict(L, 1); + + if (READ_ONCE(shdict->dead)) + return kvcache_result(L, -ESRCH); - return kvcache_incr(L, shdict, NULL); + return kvcache_incr(L, &shdict->dict, NULL); } static int shdict_index(lua_State *L) { - struct kvcache_dict *shdict = toshdict(L, 1); + struct lua_lsm_module_shdict *shdict = toshdict(L, 1); /* metatable[key] */ if (lua_getmetatable(L, 1) == 0) { @@ -628,24 +661,33 @@ static int shdict_index(lua_State *L) if (!lua_isnoneornil(L, -1)) return 1; - return kvcache_get(L, shdict, NULL); + if (READ_ONCE(shdict->dead)) + return kvcache_result(L, -ESRCH); + + return kvcache_get(L, &shdict->dict, NULL); } static int shdict_tostring(lua_State *L) { - struct kvcache_dict *shdict = toshdict(L, 1); + struct lua_lsm_module_shdict *shdict = toshdict(L, 1); unsigned long flags; - read_lock_irqsave(&shdict->lock, flags); + if (READ_ONCE(shdict->dead)) { + lua_pushliteral(L, "shdict (dead)"); + return 1; + } + + read_lock_irqsave(&shdict->dict.lock, flags); lua_pushfstring(L, "shdict (%d / %d)", - atomic_read(&shdict->count), shdict->capacity); - read_unlock_irqrestore(&shdict->lock, flags); + atomic_read(&shdict->dict.count), + shdict->dict.capacity); + read_unlock_irqrestore(&shdict->dict.lock, flags); return 1; } static int shdict_gc(lua_State *L) { - __log_info_ratelimited("shdict already freed by unregister\n"); + lua_lsm_shdict_put(toshdict(L, 1)); return 0; } diff --git a/security/lua/kvcache.h b/security/lua/kvcache.h index 85078f3e5e7e..cf2e9636f364 100644 --- a/security/lua/kvcache.h +++ b/security/lua/kvcache.h @@ -19,6 +19,7 @@ #define CACHE_CAPACITY 1024 struct lua_lsm_module; +struct lua_lsm_module_shdict; struct kvcache_node { const char *key; @@ -67,11 +68,15 @@ int lua_object_newindex(lua_State *L, struct kvcache_dict *dict); #define METH_SHARED_DICT "meth_shared_dict" #define newshdict(L) \ - ((struct kvcache_dict **)newcptr((L), METH_SHARED_DICT)) + ((struct lua_lsm_module_shdict **) \ + newcptr((L), METH_SHARED_DICT)) #define toshdictp(L, idx) \ - ((struct kvcache_dict **)luaL_checkudata((L), (idx), METH_SHARED_DICT)) + ((struct lua_lsm_module_shdict **) \ + luaL_checkudata((L), (idx), METH_SHARED_DICT)) #define toshdict(L, idx) (*toshdictp(L, idx)) +void lua_lsm_shdict_get(struct lua_lsm_module_shdict *shdict); +void lua_lsm_shdict_put(struct lua_lsm_module_shdict *shdict); int shdict_init(lua_State *L); #endif /* ! _SECURITY_LUA_LSM_KVCACHE_H */ diff --git a/security/lua/lsm.c b/security/lua/lsm.c index 003c0a94b782..43f8fe1e0490 100644 --- a/security/lua/lsm.c +++ b/security/lua/lsm.c @@ -265,6 +265,15 @@ static inline void lvm_stats_memalloc(struct lvm_state *lvm, void *ptr, static DEFINE_PER_CPU(struct lvm_state *, irq_lvms); +static void lua_lsm_module_queue_destroy(void); +static void lua_lsm_module_retire_locked(struct lua_lsm_module *module); +static void lua_lsm_module_destroy_workfn(struct work_struct *work); +static DECLARE_WORK(lua_lsm_module_destroy_work, + lua_lsm_module_destroy_workfn); +static DEFINE_SPINLOCK(lua_lsm_module_destroy_lock); +static bool lua_lsm_module_destroy_running; +static bool lua_lsm_module_destroy_rescan; + static int lua_state_alloc(struct lvm_state *lvm); static void lua_state_free(struct lvm_state *lvm); @@ -272,6 +281,7 @@ static void lua_state_free(struct lvm_state *lvm); #define LUA_LVM_POOL_MAX_LIMIT 256 static unsigned int lua_lvm_pool_max __read_mostly = LUA_LVM_POOL_MAX_DEFAULT; +static atomic_t modules_unloading = ATOMIC_INIT(0); static int __init lua_lvm_pool_max_setup(char *str) { @@ -423,6 +433,90 @@ static void lvm_vm_reset(struct lvm_state *lvm) lua_gc(L, LUA_GCCOLLECT, 0); } +static int lvm_remove_module(lua_State *L, struct lua_lsm_module *module) +{ + int err = -ENOENT; + int modules_idx; + + /* registry._MODULES[modname] = nil */ + lua_getfield(L, LUA_REGISTRYINDEX, "_MODULES"); + if (lua_istable(L, -1)) { + modules_idx = lua_gettop(L); + lua_pushstring(L, module->name); + lua_rawget(L, modules_idx); + if (lua_istable(L, -1)) { + lua_pop(L, 1); + + lua_pushstring(L, module->name); + lua_pushnil(L); + lua_rawset(L, modules_idx); + + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + if (lua_istable(L, -1)) { + lua_pushstring(L, module->name); + lua_pushnil(L); + lua_rawset(L, -3); + } + lua_pop(L, 1); + + /* performs a full garbage-collection cycle. */ + lua_gc(L, LUA_GCCOLLECT, 0); + err = 0; + } else { + lua_pop(L, 1); + } + } + lua_pop(L, 1); + return err; +} + +static int lvm_purge_module(lua_State *L, struct lua_lsm_module *module) +{ + int nloaded; + int err; + + if (!module) + return -ENOENT; + + err = lvm_remove_module(L, module); + if (!err) { + nloaded = atomic_dec_return(&module->nloaded); + WARN_ON_ONCE(nloaded < 0); + if (nloaded == 0 && READ_ONCE(module->state) == LMS_STATE_ZOMBIE) + lua_lsm_module_queue_destroy(); + + __log_info("<%s>: %d-%d purged module <%s>, nloaded = %d\n", + current->comm, task_tgid_nr(current), + task_pid_nr(current), module->name, nloaded); + } + return err; +} + +static bool lvm_module_needs_purge(struct lua_lsm_module *module) +{ + enum lua_lsm_module_state state = READ_ONCE(module->state); + + return atomic_read(&module->nloaded) > 0 && + (state == LMS_STATE_GOING || state == LMS_STATE_ZOMBIE); +} + +static void lvm_purge_unloading(lua_State *L) +{ + struct lua_lsm_module *module; + int idx; + + if (atomic_read(&modules_unloading) == 0) + return; + + idx = srcu_read_lock(&modules_ss); + list_for_each_entry_srcu(module, &lsm_modules, list, + srcu_read_lock_held(&modules_ss)) { + if (lvm_module_needs_purge(module)) + lvm_purge_module(L, module); + } + srcu_read_unlock(&modules_ss, idx); +} + static lua_State * lvm_get_from_task(const struct task_struct *task, bool exclusive) { @@ -453,12 +547,17 @@ static void lvm_put_to_task(const struct task_struct *task, lua_State *L) lua_State *lvm_get(void) { + lua_State *L; + BUG_ON(in_nmi() || in_hardirq()); - if (in_task()) - return lvm_get_from_task(current, false); - else + if (in_task()) { + L = lvm_get_from_task(current, false); + lvm_purge_unloading(L); + return L; + } else { return get_cpu_var(irq_lvms)->L; + } } void lvm_put(lua_State *L) @@ -474,8 +573,10 @@ void lvm_put(lua_State *L) static int lua_shared_index(lua_State *L) { const char *name = luaL_checkstring(L, 2); - struct lua_lsm_module_shdict *shdict, *shtmp; + struct lua_lsm_module_shdict *shdict = NULL, *new = NULL, *shtmp; + struct lua_lsm_module_shdict **slot; struct lua_lsm_module *module; + unsigned long flags; int found = 0; __log_info_ratelimited("READ shared table, [%s] %s\n", @@ -489,29 +590,40 @@ static int lua_shared_index(lua_State *L) return 0; } module = lua_touserdata(L, -1); + if (READ_ONCE(module->state) != LMS_STATE_LIVE) + return 0; - rcu_read_lock(); - list_for_each_entry_rcu(shdict, &module->shdicts, list) { - if (strcmp(shdict->name, name) == 0) { + lua_pushvalue(L, 2); + slot = newshdict(L); + + spin_lock_irqsave(&module->shdict_lock, flags); + list_for_each_entry(shtmp, &module->shdicts, list) { + if (strcmp(shtmp->name, name) == 0) { found = 1; break; } } - rcu_read_unlock(); + if (found && !READ_ONCE(shtmp->dead)) { + lua_lsm_shdict_get(shtmp); + shdict = shtmp; + } + spin_unlock_irqrestore(&module->shdict_lock, flags); - if (!found) { - unsigned long flags; + if (found && !shdict) + goto err_pop_userdata; + + if (!shdict) { size_t l = strlen(name); - shdict = kmalloc(struct_size(shdict, name, l + 1), - lua_lsm_gfp()); - if (!shdict) { + new = kzalloc(struct_size(new, name, l + 1), lua_lsm_gfp()); + if (!new) { __log_err("No memory\n"); - return 0; + goto err_pop_userdata; } - kvcache_dict_init(&shdict->dict); - memcpy(shdict->name, name, l); - shdict->name[l] = '\0'; + refcount_init(&new->refcount, 1); + kvcache_dict_init(&new->dict); + memcpy(new->name, name, l); + new->name[l] = '\0'; spin_lock_irqsave(&module->shdict_lock, flags); list_for_each_entry(shtmp, &module->shdicts, list) { @@ -520,28 +632,37 @@ static int lua_shared_index(lua_State *L) break; } } - if (!found) { + if (found) { + if (!READ_ONCE(shtmp->dead)) { + lua_lsm_shdict_get(shtmp); + shdict = shtmp; + } + } else if (READ_ONCE(module->state) == LMS_STATE_LIVE) { atomic_inc(&module->shdict_count); - list_add_tail_rcu(&shdict->list, &module->shdicts); + list_add_tail_rcu(&new->list, &module->shdicts); + lua_lsm_shdict_get(new); + shdict = new; + new = NULL; } spin_unlock_irqrestore(&module->shdict_lock, flags); - if (found) { - kvcache_dict_free(&shdict->dict); - kfree(shdict); - - shdict = shtmp; - } + lua_lsm_shdict_put(new); } + if (!shdict) + goto err_pop_userdata; + /* shared[name] = shdict */ - lua_pushvalue(L, 2); - *newshdict(L) = &shdict->dict; + *slot = shdict; lua_rawset(L, 1); lua_settop(L, 2); lua_rawget(L, 1); return 1; + +err_pop_userdata: + lua_settop(L, 2); + return 0; } static int lua_shared_newindex(lua_State *L) @@ -637,6 +758,8 @@ static int lua_modules_index(lua_State *L) /* module queries are always run with a read lock */ list_for_each_entry_srcu(module, &lsm_modules, list, srcu_read_lock_held(&modules_ss)) { + if (READ_ONCE(module->state) != LMS_STATE_LIVE) + continue; if (strcmp(module->name, key) != 0) continue; @@ -667,6 +790,7 @@ static int lua_modules_index(lua_State *L) static void lua_modules_free(struct task_struct *task, lua_State *L) { struct lua_lsm_module *module; + int nloaded; lua_getfield(L, LUA_REGISTRYINDEX, "_MODULES"); if (!lua_istable(L, -1)) { @@ -674,14 +798,18 @@ static void lua_modules_free(struct task_struct *task, lua_State *L) return; } - list_for_each_entry_srcu(module, &lsm_modules, list, srcu_read_lock_held(&modules_ss)) { + list_for_each_entry_srcu(module, &lsm_modules, list, + srcu_read_lock_held(&modules_ss)) { lua_pushstring(L, module->name); lua_rawget(L, -2); if (lua_istable(L, -1)) { - atomic_dec(&module->nloaded); + nloaded = atomic_dec_return(&module->nloaded); + WARN_ON_ONCE(nloaded < 0); + if (nloaded == 0 && READ_ONCE(module->state) == LMS_STATE_ZOMBIE) + lua_lsm_module_queue_destroy(); __log_info("<%s>: %d-%d freed module <%s>, nloaded = %d\n", task->comm, task_tgid_nr(task), task_pid_nr(task), - module->name, atomic_read(&module->nloaded)); + module->name, nloaded); } lua_pop(L, 1); } @@ -882,7 +1010,7 @@ int lua_lsm_module_register(const char *code, size_t len) if (!module) goto err_free_lua; - module->state = LMS_STATE_COMING; + WRITE_ONCE(module->state, LMS_STATE_COMING); __BITMAP_ZERO(&module->hookfuncs); /* traversal the result table */ @@ -1000,6 +1128,7 @@ int lua_lsm_module_register(const char *code, size_t len) goto err_free_module; memcpy(module->chunk, chunk, chunk_len); module->chunk_len = chunk_len; + atomic_set(&module->nloaded, 0); INIT_LIST_HEAD(&module->shdicts); spin_lock_init(&module->shdict_lock); @@ -1022,7 +1151,7 @@ int lua_lsm_module_register(const char *code, size_t len) atomic_inc(&lua_lsm_hook_stats[i].nhooks); } - module->state = LMS_STATE_LIVE; + WRITE_ONCE(module->state, LMS_STATE_LIVE); list_add_tail_rcu(&module->list, &lsm_modules); } mutex_unlock(&modules_mutex); @@ -1047,85 +1176,6 @@ int lua_lsm_module_register(const char *code, size_t len) return err; } -static int lvm_remove_module(lua_State *L, struct lua_lsm_module *module) -{ - int err = -ENOENT; - /* registry._MODULES[modname] = nil */ - lua_getfield(L, LUA_REGISTRYINDEX, "_MODULES"); - if (lua_istable(L, -1)) { - lua_pushstring(L, module->name); - lua_rawget(L, -2); - if (lua_istable(L, -1)) { - lua_pop(L, 1); - - lua_pushstring(L, module->name); - lua_pushnil(L); - lua_rawset(L, -3); - - /* performs a full garbage-collection cycle. */ - lua_gc(L, LUA_GCCOLLECT, 0); - err = 0; - } else { - lua_pop(L, 1); - } - } - lua_pop(L, 1); - return err; -} - -static int task_remove_module(struct task_struct *task, void *arg) -{ - struct lua_lsm_module *module = arg; - lua_State *L; - int err; - - if (task_curr(task) && task != current) - return -EBUSY; - - L = lvm_get_from_task(task, true); - if (!L) - return -EAGAIN; - - err = lvm_remove_module(L, module); - lvm_put_to_task(task, L); - return err; -} - -static int tasks_lvm_remove_module(struct lua_lsm_module *module, int *nbusy) -{ - struct task_struct *g, *task; - int count = 0; - int err; - - *nbusy = 0; - /* remove loaded module from every Lua VM */ - read_lock(&tasklist_lock); - for_each_process_thread(g, task) { - if (task == current) - err = task_remove_module(task, module); - else - err = task_call_func(task, task_remove_module, module); - - if (!err) { - count++; - __log_info("<%s>: err = [ OK ] \t<%s>: %d-%d\n", module->name, - task->comm, task_tgid_nr(task), task_pid_nr(task)); - } else if (err == -EBUSY || err == -EAGAIN) { - *nbusy += 1; - __log_info("<%s>: err = %s \t<%s>: %d-%d\n", - module->name, err == -EBUSY ? "EBUSY" : "EAGAIN", - task->comm, task_tgid_nr(task), task_pid_nr(task)); - } else { - __log_info_ratelimited("<%s>: err = %s \t<%s>: %d-%d\n", - module->name, - err == -ENOENT ? "[ENOENT]" : "unknown", - task->comm, task_tgid_nr(task), task_pid_nr(task)); - } - } - read_unlock(&tasklist_lock); - return count; -} - /* * Due to the limitations of schedule_on_each_cpu(), global variables * are used to pass parameters to the callback function. @@ -1145,7 +1195,7 @@ static void softirq_lvm_remove_module(struct work_struct *work) * changing the Lua VM environment. */ local_bh_disable(); - err = lvm_remove_module(per_cpu(irq_lvms, cpu)->L, module); + err = lvm_purge_module(per_cpu(irq_lvms, cpu)->L, module); if (!err) { atomic_inc(&work_ctx_remove_count); __log_info("<%s>: err = [ OK ] \t, count = %d\n", @@ -1154,15 +1204,137 @@ static void softirq_lvm_remove_module(struct work_struct *work) local_bh_enable(); } -int lua_lsm_module_unregister(const char *name) +/* + * Lazy VM teardown can drop the final module reference after unregister() + * already returned -EBUSY. Final removal must run outside SRCU read-side + * sections, so queue a worker to retry the last unregister step. The + * worker detaches one drained zombie under modules_mutex, then waits for + * the SRCU grace period and frees it without holding the mutex. + */ +static bool lua_lsm_module_drained(struct lua_lsm_module *module) +{ + if (READ_ONCE(module->state) != LMS_STATE_ZOMBIE) + return false; + if (atomic_read(&module->nloaded) != 0) + return false; + + return true; +} + +static struct lua_lsm_module *lua_lsm_module_take_drained_locked(void) +{ + struct lua_lsm_module *module, *tmp; + + list_for_each_entry_safe(module, tmp, &lsm_modules, list) { + if (!lua_lsm_module_drained(module)) + continue; + + lua_lsm_module_retire_locked(module); + return module; + } + + return NULL; +} + +static void lua_lsm_module_destroy(struct lua_lsm_module *module) +{ + synchronize_srcu(&modules_ss); + lua_lsm_module_free(module); +} + +static void lua_lsm_module_queue_destroy(void) +{ + unsigned long flags; + bool queue = false; + + spin_lock_irqsave(&lua_lsm_module_destroy_lock, flags); + lua_lsm_module_destroy_rescan = true; + if (!lua_lsm_module_destroy_running) { + lua_lsm_module_destroy_running = true; + queue = true; + } + spin_unlock_irqrestore(&lua_lsm_module_destroy_lock, flags); + + if (queue) + schedule_work(&lua_lsm_module_destroy_work); +} + +static void lua_lsm_module_destroy_workfn(struct work_struct *work) { struct lua_lsm_module *module; + unsigned long flags; + + for (;;) { + spin_lock_irqsave(&lua_lsm_module_destroy_lock, flags); + lua_lsm_module_destroy_rescan = false; + spin_unlock_irqrestore(&lua_lsm_module_destroy_lock, flags); + + for (;;) { + mutex_lock(&modules_mutex); + module = lua_lsm_module_take_drained_locked(); + mutex_unlock(&modules_mutex); + + if (!module) + break; + + lua_lsm_module_destroy(module); + } + + spin_lock_irqsave(&lua_lsm_module_destroy_lock, flags); + if (!lua_lsm_module_destroy_rescan) { + lua_lsm_module_destroy_running = false; + spin_unlock_irqrestore(&lua_lsm_module_destroy_lock, flags); + break; + } + spin_unlock_irqrestore(&lua_lsm_module_destroy_lock, flags); + } +} + +static void lua_lsm_module_unlink_shdicts(struct lua_lsm_module *module) +{ struct lua_lsm_module_shdict *shdict, *tmp; - int count = 0, nloaded, nbusy; - unsigned int cpu; + unsigned long flags; + + spin_lock_irqsave(&module->shdict_lock, flags); + list_for_each_entry_safe(shdict, tmp, &module->shdicts, list) { + list_del_rcu(&shdict->list); + atomic_dec(&module->shdict_count); + lua_lsm_shdict_put(shdict); + } + spin_unlock_irqrestore(&module->shdict_lock, flags); +} + +static void lua_lsm_module_disable_hooks(struct lua_lsm_module *module) +{ + int i; + + for (i = 0; lua_lsm_hook_stats[i].name; i++) { + if (__BITMAP_ISSET(i, &module->hookfuncs)) + atomic_dec(&lua_lsm_hook_stats[i].nhooks); + } +} + +static void lua_lsm_module_retire_locked(struct lua_lsm_module *module) +{ + WARN_ON_ONCE(atomic_read(&module->nloaded) != 0); + WARN_ON_ONCE(READ_ONCE(module->state) != LMS_STATE_GOING && + READ_ONCE(module->state) != LMS_STATE_ZOMBIE); + + lua_lsm_module_disable_hooks(module); + list_del_rcu(&module->list); + lua_lsm_module_unlink_shdicts(module); + atomic_dec(&modules_unloading); +} + +int lua_lsm_module_unregister(const char *name) +{ + struct lua_lsm_module *module; + struct lua_lsm_module *retired = NULL; + struct lua_lsm_module_shdict *shdict; + int count = 0, nloaded, remaining; + unsigned long flags; int found = 0; int err; - int i; mutex_lock(&modules_mutex); list_for_each_entry(module, &lsm_modules, list) { @@ -1171,13 +1343,13 @@ int lua_lsm_module_unregister(const char *name) break; } } - if (found && module->state == LMS_STATE_LIVE) { - module->state = LMS_STATE_GOING; - - for (i = 0; lua_lsm_hook_stats[i].name; i++) { - if (__BITMAP_ISSET(i, &module->hookfuncs)) - atomic_dec(&lua_lsm_hook_stats[i].nhooks); - } + if (found && READ_ONCE(module->state) == LMS_STATE_LIVE) { + /* + * Keep the hook counts until final removal so tasks keep + * entering Lua-LSM and can purge their own VMs. + */ + WRITE_ONCE(module->state, LMS_STATE_GOING); + atomic_inc(&modules_unloading); } if (!found) { @@ -1185,26 +1357,38 @@ int lua_lsm_module_unregister(const char *name) return -ENOENT; } + if (lua_lsm_module_drained(module)) + goto out_detach; + pr_info("Prepare to unregister module <%s> ...\n", name); synchronize_srcu(&modules_ss); - list_for_each_entry_safe(shdict, tmp, &module->shdicts, list) { - list_del(&shdict->list); - kvcache_dict_free(&shdict->dict); - kfree(shdict); - atomic_dec(&module->shdict_count); - } + /* + * Existing Lua userdata may outlive the VM purge attempt. Tombstone + * shared dicts now and drop the module list refs only once unregister + * can complete. + */ + spin_lock_irqsave(&module->shdict_lock, flags); + list_for_each_entry(shdict, &module->shdicts, list) + WRITE_ONCE(shdict->dead, true); + spin_unlock_irqrestore(&module->shdict_lock, flags); kvcache_module_nodes_gc(module); nloaded = atomic_read(&module->nloaded); - /* remove loaded module from every Lua VM */ if (nloaded > 0) { - count += tasks_lvm_remove_module(module, &nbusy); + lua_State *L = lvm_get_from_task(current, true); + + if (L) { + err = lvm_purge_module(L, module); + lvm_put_to_task(current, L); + if (!err) + count++; + } - __log_info("Unregister module <%s> from task, freed = %d/%d, nbusy = %d\n", - name, count, nloaded, nbusy); + __log_info("Unregister module <%s> from current task, freed = %d/%d\n", + name, count, nloaded); } /* @@ -1212,7 +1396,7 @@ int lua_lsm_module_unregister(const char *name) * so the nloaded will be updated in the air. */ nloaded = atomic_read(&module->nloaded); - if (count < nloaded) { + if (nloaded > 0) { /* * Since global variables are used, locking ensures that only * one instance of the softirq LuaVM offload is executed. @@ -1228,56 +1412,30 @@ int lua_lsm_module_unregister(const char *name) name, count, nloaded); } - nloaded = atomic_read(&module->nloaded); - if (count < nloaded) { - /* ditto for the idle 'swapper' tasks */ - cpus_read_lock(); - for_each_possible_cpu(cpu) { - /* TODO: remove 'swapper' tasks Lua VM */ - err = task_call_func(idle_task(cpu), task_remove_module, module); - if (!err) - count++; - } - cpus_read_unlock(); - - __log_info("Unregister module <%s> from swapper, freed = %d/%d\n", - name, count, atomic_read(&module->nloaded)); - } - - nloaded = atomic_read(&module->nloaded); - if (count < nloaded && nbusy > 0) { - for (i = 1; i <= 5; i++) { - count += tasks_lvm_remove_module(module, &nbusy); - WARN_ON(count > nloaded); - - __log_info("Unregister module <%s> from task, freed = %d/%d, nbusy = %d, loop = %d\n", - name, count, nloaded, nbusy, i); + remaining = atomic_read(&module->nloaded); + if (remaining == 0) + goto out_detach; - nloaded = atomic_read(&module->nloaded); - if (count == nloaded || nbusy == 0) - break; + WRITE_ONCE(module->state, LMS_STATE_ZOMBIE); + if (lua_lsm_module_drained(module)) + goto out_detach; - msleep(500 * i); + remaining = atomic_read(&module->nloaded); + err = -EBUSY; + goto out_unlock; - nloaded = atomic_read(&module->nloaded); - if (count == nloaded) - break; - } - } - - if (atomic_sub_return(count, &module->nloaded) == 0) { - list_del_rcu(&module->list); - synchronize_srcu(&modules_ss); - lua_lsm_module_free(module); - err = 0; - } else { - module->state = LMS_STATE_ZOMBIE; - err = -EBUSY; - } +out_detach: + remaining = 0; + lua_lsm_module_retire_locked(module); + retired = module; + err = 0; +out_unlock: mutex_unlock(&modules_mutex); + if (retired) + lua_lsm_module_destroy(retired); - pr_info("Unregistered module <%s> from %d/%d Lua VMs, vm_nusage = %d\n", - name, count, nloaded, atomic_read(&vm_nusage)); + pr_info("Unregister module <%s>: purged %d Lua VMs, remaining = %d, vm_nusage = %d\n", + name, count, remaining, atomic_read(&vm_nusage)); return err; } diff --git a/security/lua/lsm.h b/security/lua/lsm.h index a575b4608326..386cdf73e300 100644 --- a/security/lua/lsm.h +++ b/security/lua/lsm.h @@ -9,6 +9,7 @@ #define _SECURITY_LUA_LSM_LSM_H #include +#include #include #include #include @@ -61,6 +62,10 @@ static inline bool lua_lsm_hook_supported(unsigned int nr) struct lua_lsm_module_shdict { struct list_head list; + /* One ref for the module list, one per cached Lua userdata. */ + atomic_t refcount; + struct rcu_head rcu; + bool dead; struct kvcache_dict dict; char name[]; }; diff --git a/security/lua/lsm_defs.h b/security/lua/lsm_defs.h index d6762e9b1a8a..12bf07292118 100644 --- a/security/lua/lsm_defs.h +++ b/security/lua/lsm_defs.h @@ -140,7 +140,7 @@ srcu_read_lock_held(&modules_ss)) { \ if (!__BITMAP_ISSET(__LL_NR_ ## NAME, &module->hookfuncs)) \ continue; \ - if (module->state != LMS_STATE_LIVE) \ + if (READ_ONCE(module->state) != LMS_STATE_LIVE) \ continue; \ lua_getfield(L, -1, module->name); \ lua_getfield(L, -1, #NAME); \