diff --git a/security/lua/kvcache.c b/security/lua/kvcache.c index 37768761b669..ef75ff302665 100644 --- a/security/lua/kvcache.c +++ b/security/lua/kvcache.c @@ -556,6 +556,30 @@ void kvcache_dict_init(struct kvcache_dict *dict) atomic_set_release(&dict->state, KVCACHE_DICT_READY); } +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; @@ -674,28 +698,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) { @@ -707,24 +740,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 bd0b1c1cea8a..feb4706218a1 100644 --- a/security/lua/kvcache.h +++ b/security/lua/kvcache.h @@ -27,6 +27,7 @@ enum kvcache_dict_state { }; struct lua_lsm_module; +struct lua_lsm_module_shdict; struct kvcache_node { const char *key; @@ -76,11 +77,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 8aae5933065a..dcdeb0b522d3 100644 --- a/security/lua/lsm.c +++ b/security/lua/lsm.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "lsm.h" #include "lua_object.h" #include "lsm_defs.h" @@ -270,6 +272,11 @@ static DEFINE_PER_CPU(struct lvm_state *, irq_lvms); static lua_State *lvm_build_lua_state(struct lvm_state *lvm); static void *lvm_alloc(void *ud, void *ptr, size_t osize, size_t nsize); +static int lua_lsm_module_drop_from_vm(struct lua_lsm_module *module); +static void lua_lsm_module_finalize_workfn(struct work_struct *work); +static DECLARE_WORK(lua_lsm_module_finalize_work, + lua_lsm_module_finalize_workfn); + static int lua_state_alloc(struct lvm_state *lvm); static void lua_state_free(struct lvm_state *lvm); @@ -277,6 +284,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) { @@ -482,6 +490,86 @@ lvm_get_task_state(const struct task_struct *task, bool create) return lvm; } +static int lvm_forget_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_drop_module(lua_State *L, struct lua_lsm_module *module) +{ + int err; + + if (!module) + return -ENOENT; + + err = lvm_forget_module(L, module); + if (!err) { + int __maybe_unused loaded_vms = lua_lsm_module_drop_from_vm(module); + + __log_info("<%s>: %d-%d dropped module <%s>, loaded_vms = %d\n", + current->comm, task_tgid_nr(current), + task_pid_nr(current), module->name, loaded_vms); + } + return err; +} + +static bool lvm_should_drop_module(struct lua_lsm_module *module) +{ + enum lua_lsm_module_state state = READ_ONCE(module->state); + + return atomic_read(&module->loaded_vm_count) > 0 && + (state == LMS_STATE_GOING || state == LMS_STATE_ZOMBIE); +} + +static void lvm_drop_unloading_modules(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_should_drop_module(module)) + lvm_drop_module(L, module); + } + srcu_read_unlock(&modules_ss, idx); +} + static lua_State * lvm_get_from_task(const struct task_struct *task, bool require_idle) { @@ -559,12 +647,18 @@ 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); + if (L) + lvm_drop_unloading_modules(L); + return L; + } else { return get_cpu_var(irq_lvms)->L; + } } void lvm_put(lua_State *L) @@ -580,8 +674,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", @@ -595,29 +691,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) { @@ -626,28 +733,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) @@ -743,6 +859,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; @@ -759,7 +877,7 @@ static int lua_modules_index(lua_State *L) /* stack: [table, thunk, key, thunk] */ lua_rawset(L, 1); /* table[key] = thunk */ - atomic_inc(&module->nloaded); + atomic_inc(&module->loaded_vm_count); return 1; /* return the thunk */ } @@ -768,10 +886,9 @@ static int lua_modules_index(lua_State *L) } /* - * When LuaVM is destroyed, iterate over the modules loaded in the VM - * and update the load count in the module. + * Drop each policy module reference recorded in a Lua VM's _MODULES table. */ -static void lua_modules_free(struct task_struct *task, lua_State *L) +static void lvm_put_loaded_modules(struct task_struct *task, lua_State *L) { struct lua_lsm_module *module; @@ -781,14 +898,20 @@ 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); - __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)); + int __maybe_unused loaded_vms = lua_lsm_module_drop_from_vm(module); + + if (task) + __log_info("<%s>: %d-%d dropped module <%s>, loaded_vms = %d\n", + task->comm, task_tgid_nr(task), task_pid_nr(task), + module->name, loaded_vms); + else + __log_info("dropped module <%s>, loaded_vms = %d\n", + module->name, loaded_vms); } lua_pop(L, 1); } @@ -998,7 +1121,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 */ @@ -1116,6 +1239,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->loaded_vm_count, 0); INIT_LIST_HEAD(&module->shdicts); spin_lock_init(&module->shdict_lock); @@ -1138,7 +1262,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); /* * Once any Lua policy has been loaded, inactive free-hook cleanup @@ -1169,92 +1293,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_task *llt = lua_lsm_task(task); - struct lua_lsm_module *module = arg; - struct lvm_state *lvm; - lua_State *L; - int err; - - if (task_curr(task) && task != current) - return -EBUSY; - - /* Unregister must not lazily create a VM for untouched tasks. */ - /* Pairs with cmpxchg() in lvm_get_task_state(). */ - lvm = smp_load_acquire(&llt->lvm); - if (!lvm || !smp_load_acquire(&lvm->L)) - return -ENOENT; - - 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 if (err != -ENOENT) { - __log_info_ratelimited("<%s>: err = %d \t<%s>: %d-%d\n", - module->name, err, - 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. @@ -1262,7 +1300,7 @@ static int tasks_lvm_remove_module(struct lua_lsm_module *module, int *nbusy) static struct lua_lsm_module *work_ctx_remove_module; static atomic_t work_ctx_remove_count; -static void softirq_lvm_remove_module(struct work_struct *work) +static void softirq_lvm_drop_module(struct work_struct *work) { struct lua_lsm_module *module = work_ctx_remove_module; int cpu = smp_processor_id(); @@ -1274,7 +1312,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_drop_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", @@ -1283,15 +1321,104 @@ static void softirq_lvm_remove_module(struct work_struct *work) local_bh_enable(); } +static bool lua_lsm_module_ready_to_finalize(struct lua_lsm_module *module) +{ + return READ_ONCE(module->state) == LMS_STATE_ZOMBIE && + atomic_read(&module->loaded_vm_count) == 0; +} + +static int lua_lsm_module_drop_from_vm(struct lua_lsm_module *module) +{ + int loaded_vms = atomic_dec_return(&module->loaded_vm_count); + + WARN_ON_ONCE(loaded_vms < 0); + if (loaded_vms == 0 && lua_lsm_module_ready_to_finalize(module)) + schedule_work(&lua_lsm_module_finalize_work); + + return loaded_vms; +} + +static void lua_lsm_module_mark_shdicts_dead(struct lua_lsm_module *module) +{ + struct lua_lsm_module_shdict *shdict; + unsigned long flags; + + 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); +} + +static void lua_lsm_module_release_shdicts(struct lua_lsm_module *module) +{ + struct lua_lsm_module_shdict *shdict, *tmp; + unsigned long flags; + + spin_lock_irqsave(&module->shdict_lock, flags); + list_for_each_entry_safe(shdict, tmp, &module->shdicts, list) { + WRITE_ONCE(shdict->dead, true); + 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_unregister_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_free_srcu(struct rcu_head *rcu) +{ + struct lua_lsm_module *module; + + module = container_of(rcu, struct lua_lsm_module, rcu); + lua_lsm_module_free(module); +} + +/* + * Remove the module from all global lookup paths. The object itself stays + * alive until pre-existing modules_ss readers have left their critical section. + */ +static void lua_lsm_module_finalize_locked(struct lua_lsm_module *module) +{ + lockdep_assert_held(&modules_mutex); + + WARN_ON_ONCE(atomic_read(&module->loaded_vm_count) != 0); + WARN_ON_ONCE(READ_ONCE(module->state) != LMS_STATE_GOING && + READ_ONCE(module->state) != LMS_STATE_ZOMBIE); + + lua_lsm_module_unregister_hooks(module); + list_del_rcu(&module->list); + lua_lsm_module_release_shdicts(module); + atomic_dec(&modules_unloading); + call_srcu(&modules_ss, &module->rcu, lua_lsm_module_free_srcu); +} + +static void lua_lsm_module_finalize_workfn(struct work_struct *work) +{ + struct lua_lsm_module *module, *tmp; + + mutex_lock(&modules_mutex); + list_for_each_entry_safe(module, tmp, &lsm_modules, list) { + if (lua_lsm_module_ready_to_finalize(module)) + lua_lsm_module_finalize_locked(module); + } + mutex_unlock(&modules_mutex); +} + int lua_lsm_module_unregister(const char *name) { struct lua_lsm_module *module; - struct lua_lsm_module_shdict *shdict, *tmp; - int count = 0, nloaded, nbusy; - unsigned int cpu; + int count = 0, loaded_vms, remaining; int found = 0; int err; - int i; mutex_lock(&modules_mutex); list_for_each_entry(module, &lsm_modules, list) { @@ -1300,14 +1427,13 @@ int lua_lsm_module_unregister(const char *name) break; } } - if (found && module->state == LMS_STATE_LIVE) { - module->state = LMS_STATE_GOING; - static_branch_dec(&lua_lsm_modules_active); - - 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 drop the module from their own VMs. + */ + WRITE_ONCE(module->state, LMS_STATE_GOING); + atomic_inc(&modules_unloading); } if (!found) { @@ -1315,34 +1441,43 @@ int lua_lsm_module_unregister(const char *name) return -ENOENT; } + if (lua_lsm_module_ready_to_finalize(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 drop attempt. Tombstone + * shared dicts now and drop the module list refs only once unregister + * can complete. + */ + lua_lsm_module_mark_shdicts_dead(module); 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); + loaded_vms = atomic_read(&module->loaded_vm_count); + if (loaded_vms > 0) { + lua_State *L = lvm_get_from_task(current, true); - __log_info("Unregister module <%s> from task, freed = %d/%d, nbusy = %d\n", - name, count, nloaded, nbusy); + if (L) { + err = lvm_drop_module(L, module); + lvm_put_to_task(current, L); + if (!err) + count++; + } + + __log_info("Unregister module <%s> from current task, freed = %d/%d\n", + name, count, loaded_vms); } /* * At this time, LuaVM may still be released asynchronously, - * so the nloaded will be updated in the air. + * so loaded_vm_count may change concurrently. */ - nloaded = atomic_read(&module->nloaded); - if (count < nloaded) { + loaded_vms = atomic_read(&module->loaded_vm_count); + if (loaded_vms > 0) { /* * Since global variables are used, locking ensures that only * one instance of the softirq LuaVM offload is executed. @@ -1350,86 +1485,84 @@ int lua_lsm_module_unregister(const char *name) work_ctx_remove_module = module; atomic_set(&work_ctx_remove_count, 0); - err = schedule_on_each_cpu(softirq_lvm_remove_module); + err = schedule_on_each_cpu(softirq_lvm_drop_module); WARN_ON(err); count += atomic_read(&work_ctx_remove_count); __log_info("Unregister module <%s> from pcpu, freed = %d/%d\n", - 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)); + name, count, loaded_vms); } - 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); - - nloaded = atomic_read(&module->nloaded); - if (count == nloaded || nbusy == 0) - break; + remaining = atomic_read(&module->loaded_vm_count); + if (remaining == 0) + goto out_detach; - msleep(500 * i); + WRITE_ONCE(module->state, LMS_STATE_ZOMBIE); + if (lua_lsm_module_ready_to_finalize(module)) + goto out_detach; - 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; - } + /* + * The module is deactivated -- its hooks no longer run -- but some Lua + * VMs still hold a reference to it. Report success: the remaining VMs + * drop it lazily on their next Lua-LSM entry, and the finalize worker + * frees it once loaded_vm_count reaches zero. Until then the module + * stays on the list and is visible as ZOMBIE in /modules. + */ + remaining = atomic_read(&module->loaded_vm_count); + err = 0; + goto out_unlock; + +out_detach: + remaining = 0; + lua_lsm_module_finalize_locked(module); + err = 0; +out_unlock: mutex_unlock(&modules_mutex); - 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>: dropped from %d Lua VMs, remaining = %d%s, vm_nusage = %d\n", + name, count, remaining, + remaining ? " (zombie, background cleanup pending)" : "", + atomic_read(&vm_nusage)); return err; } +static const char *lua_lsm_module_state_name(enum lua_lsm_module_state state) +{ + switch (state) { + case LMS_STATE_LIVE: + return "live"; + case LMS_STATE_COMING: + return "coming"; + case LMS_STATE_GOING: + return "going"; + case LMS_STATE_ZOMBIE: + return "zombie"; + default: + return "?"; + } +} + int modules_show(struct seq_file *m, void *v) { struct lua_lsm_module *module; int idx; seq_printf(m, "modules for lua-lsm\n"); - seq_printf(m, "%-20s %-10s %6s %4s %5s %6s %6s %-34s\n", + seq_printf(m, "%-20s %-10s %6s %4s %5s %6s %6s %-7s %-34s\n", "name", "license", "size", "nlsm", - "nload", "shdict", "kvnode", "author"); + "nload", "shdict", "kvnode", "state", "author"); seq_printf(m, "%s\n", TABLINE); idx = srcu_read_lock(&modules_ss); list_for_each_entry_srcu(module, &lsm_modules, list, srcu_read_lock_held(&modules_ss)) { - seq_printf(m, "%-20s %-10s %6zu %4d %5d %6d %6d %-34s\n", + seq_printf(m, "%-20s %-10s %6zu %4d %5d %6d %6d %-7s %-34s\n", module->name, module->license, module->chunk_len, - module->nhooks, atomic_read(&module->nloaded), + module->nhooks, atomic_read(&module->loaded_vm_count), atomic_read(&module->shdict_count), - atomic_read(&module->kvnodes_count), module->author); + atomic_read(&module->kvnodes_count), + lua_lsm_module_state_name(READ_ONCE(module->state)), + module->author); } srcu_read_unlock(&modules_ss, idx); return 0; @@ -1459,8 +1592,11 @@ void task_blob_free(struct task_struct *task) /* Pairs with cmpxchg() in lvm_get_task_state(). */ lvm = smp_load_acquire(&llt->lvm); if (lvm && READ_ONCE(lvm->L) && lvm->dirty) { - lua_modules_free(task, lvm->L); + int idx = srcu_read_lock(&modules_ss); + + lvm_put_loaded_modules(task, lvm->L); lvm_vm_reset(lvm); + srcu_read_unlock(&modules_ss, idx); lvm->dirty = false; } diff --git a/security/lua/lsm.h b/security/lua/lsm.h index 95082c2e54cb..8d3595ec0d83 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 @@ -94,6 +95,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[]; }; @@ -113,12 +118,14 @@ struct lua_lsm_module { int version; enum lua_lsm_module_state state; struct list_head list; + struct rcu_head rcu; __BITMAP_TYPE(, uint32_t, __LL_NR_MAX) hookfuncs; int nhooks; char *chunk; size_t chunk_len; - atomic_t nloaded; + /* Lua VMs that currently have this module in their _MODULES table. */ + atomic_t loaded_vm_count; struct list_head shdicts; /* Protects shdicts and shdict_count. */ diff --git a/security/lua/lsm_defs.h b/security/lua/lsm_defs.h index 47966158575b..6cd8e77dd2b6 100644 --- a/security/lua/lsm_defs.h +++ b/security/lua/lsm_defs.h @@ -275,7 +275,7 @@ static inline bool lua_lsm_hook_has_inactive_cleanup(unsigned int nr) 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); \