core: do not crash setting the log level before the process table exists - #4121
core: do not crash setting the log level before the process table exists#4121Lt-Flash wants to merge 1 commit into
Conversation
set_proc_log_level() and reset_proc_log_level() segfault when called from a module's mod_init(). init_modules() runs several steps before init_multi_proc_support(), so pt is still NULL, and __set_proc_log_level() writes pt[proc_idx].log_level unconditionally. reset_proc_log_level() fails the same way one level down: default_log_level is a static pointer that init_log_level() has not filled in yet, so it dereferences NULL. This is easy to hit. Temporarily lowering the log level around a call that is expected to fail is the natural way for a module to keep an expected error out of its startup output, and the API gives no hint that it is unusable at the point most modules would reach for it. Guards each of the affected setters. Where the intent can still be honoured it is, rather than silently dropping the request: log_level already points at the static holder before init_log_level() runs, and there is only one process at that stage, so __set_proc_log_level() sets the level through it and reset_proc_log_level() restores the value logging started with. __set_proc_default_log_level() updates that same holder. set_global_log_level() additionally guards log_level_global, which init_log_level() allocates - before that, counted_max_processes is 0 so its loop is a no-op and the shared value does not exist yet, so it applies the level to the current process instead. suppress_proc_log_event() and reset_proc_log_event() return early: the event consumer they guard is not running yet either. Verified by calling set_proc_log_level() followed by reset_proc_log_level() from a module mod_init(): the same module segfaults on an unpatched core and starts normally on a patched one.
|
Hi @Lt-Flash - usually the |
|
Hi @bogdan-iancu , So the need came from the startup selftests (arena_selftest / htable_selftest modparams, which run in mod_init). To get coverage of the rejection paths, the test has to actually trigger them — a non-numeric add and an oversize store. Both legitimately log LM_ERR, because in normal operation they are errors. But that meant a completely passing selftest emitted ERROR lines, which is exactly the kind of thing that makes an operator think the module is broken at startup. The obvious way to silence them for the duration is to drop this process's log level around the test and restore it after — i.e. set_proc_log_level() / reset_proc_log_level(). Which segfaults, because it writes pt[process_no] and the process table doesn't exist during mod_init. |
What
set_proc_log_level()andreset_proc_log_level()segfault when called from a module'smod_init().Root cause
init_modules()runs several steps beforeinit_multi_proc_support(), so the process table is still NULL while every module'smod_init()executes — and the setter writes through it unconditionally:reset_proc_log_level()fails the same way one level down:default_log_levelis a static pointer thatinit_log_level()has not filled in yet, so it dereferences NULL.Why it is worth guarding
Temporarily lowering the log level around a call that is expected to fail is the natural way for a module to keep a benign startup error out of its output — and the API gives no hint that it is unusable at exactly the point most modules would reach for it. The failure is a hard crash at startup, not a diagnostic.
The fix
Guards each affected setter. Where the intent can still be honoured it is, rather than silently dropping the request:
__set_proc_log_level()—log_levelalready points at the static holder beforeinit_log_level()runs, and there is only one process at that stage, so the level is applied through it.reset_proc_log_level()— restores the value logging started with (log_level_holder).__set_proc_default_log_level()— updates that same holder.set_global_log_level()— additionally guardslog_level_global, whichinit_log_level()allocates. Before that,counted_max_processesis 0 so its loop is a no-op and the shared value does not exist, so it applies the level to the current process instead.suppress_proc_log_event()/reset_proc_log_event()— return early; the event consumer they guard is not running yet either.No behaviour changes once the process table exists — every guard is a NULL check on state that is non-NULL from
init_multi_proc_support()onward.Testing
A/B tested with a module calling
set_proc_log_level()followed byreset_proc_log_level()from itsmod_init(): the same module segfaults on an unpatched core and starts normally on a patched one.