kfence: allow use of a deferrable timer

Allow the use of a deferrable timer, which does not force CPU wake-ups
when the system is idle.  A consequence is that the sample interval
becomes very unpredictable, to the point that it is not guaranteed that
the KFENCE KUnit test still passes.

Nevertheless, on power-constrained systems this may be preferable, so
let's give the user the option should they accept the above trade-off.

Link: https://lkml.kernel.org/r/20220308141415.3168078-1-elver@google.com
Signed-off-by: Marco Elver <elver@google.com>
Reviewed-by: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Marco Elver 2022-03-22 14:48:25 -07:00 committed by Linus Torvalds
parent 3cb1c9620e
commit 737b6a10ac
3 changed files with 37 additions and 2 deletions

View File

@ -41,6 +41,18 @@ guarded by KFENCE. The default is configurable via the Kconfig option
``CONFIG_KFENCE_SAMPLE_INTERVAL``. Setting ``kfence.sample_interval=0`` ``CONFIG_KFENCE_SAMPLE_INTERVAL``. Setting ``kfence.sample_interval=0``
disables KFENCE. disables KFENCE.
The sample interval controls a timer that sets up KFENCE allocations. By
default, to keep the real sample interval predictable, the normal timer also
causes CPU wake-ups when the system is completely idle. This may be undesirable
on power-constrained systems. The boot parameter ``kfence.deferrable=1``
instead switches to a "deferrable" timer which does not force CPU wake-ups on
idle systems, at the risk of unpredictable sample intervals. The default is
configurable via the Kconfig option ``CONFIG_KFENCE_DEFERRABLE``.
.. warning::
The KUnit test suite is very likely to fail when using a deferrable timer
since it currently causes very unpredictable sample intervals.
The KFENCE memory pool is of fixed size, and if the pool is exhausted, no The KFENCE memory pool is of fixed size, and if the pool is exhausted, no
further KFENCE allocations occur. With ``CONFIG_KFENCE_NUM_OBJECTS`` (default further KFENCE allocations occur. With ``CONFIG_KFENCE_NUM_OBJECTS`` (default
255), the number of available guarded objects can be controlled. Each object 255), the number of available guarded objects can be controlled. Each object

View File

@ -45,6 +45,18 @@ config KFENCE_NUM_OBJECTS
pages are required; with one containing the object and two adjacent pages are required; with one containing the object and two adjacent
ones used as guard pages. ones used as guard pages.
config KFENCE_DEFERRABLE
bool "Use a deferrable timer to trigger allocations"
help
Use a deferrable timer to trigger allocations. This avoids forcing
CPU wake-ups if the system is idle, at the risk of a less predictable
sample interval.
Warning: The KUnit test suite fails with this option enabled - due to
the unpredictability of the sample interval!
Say N if you are unsure.
config KFENCE_STATIC_KEYS config KFENCE_STATIC_KEYS
bool "Use static keys to set up allocations" if EXPERT bool "Use static keys to set up allocations" if EXPERT
depends on JUMP_LABEL depends on JUMP_LABEL

View File

@ -95,6 +95,10 @@ module_param_cb(sample_interval, &sample_interval_param_ops, &kfence_sample_inte
static unsigned long kfence_skip_covered_thresh __read_mostly = 75; static unsigned long kfence_skip_covered_thresh __read_mostly = 75;
module_param_named(skip_covered_thresh, kfence_skip_covered_thresh, ulong, 0644); module_param_named(skip_covered_thresh, kfence_skip_covered_thresh, ulong, 0644);
/* If true, use a deferrable timer. */
static bool kfence_deferrable __read_mostly = IS_ENABLED(CONFIG_KFENCE_DEFERRABLE);
module_param_named(deferrable, kfence_deferrable, bool, 0444);
/* The pool of pages used for guard pages and objects. */ /* The pool of pages used for guard pages and objects. */
char *__kfence_pool __read_mostly; char *__kfence_pool __read_mostly;
EXPORT_SYMBOL(__kfence_pool); /* Export for test modules. */ EXPORT_SYMBOL(__kfence_pool); /* Export for test modules. */
@ -740,6 +744,8 @@ late_initcall(kfence_debugfs_init);
/* === Allocation Gate Timer ================================================ */ /* === Allocation Gate Timer ================================================ */
static struct delayed_work kfence_timer;
#ifdef CONFIG_KFENCE_STATIC_KEYS #ifdef CONFIG_KFENCE_STATIC_KEYS
/* Wait queue to wake up allocation-gate timer task. */ /* Wait queue to wake up allocation-gate timer task. */
static DECLARE_WAIT_QUEUE_HEAD(allocation_wait); static DECLARE_WAIT_QUEUE_HEAD(allocation_wait);
@ -762,7 +768,6 @@ static DEFINE_IRQ_WORK(wake_up_kfence_timer_work, wake_up_kfence_timer);
* avoids IPIs, at the cost of not immediately capturing allocations if the * avoids IPIs, at the cost of not immediately capturing allocations if the
* instructions remain cached. * instructions remain cached.
*/ */
static struct delayed_work kfence_timer;
static void toggle_allocation_gate(struct work_struct *work) static void toggle_allocation_gate(struct work_struct *work)
{ {
if (!READ_ONCE(kfence_enabled)) if (!READ_ONCE(kfence_enabled))
@ -790,7 +795,6 @@ static void toggle_allocation_gate(struct work_struct *work)
queue_delayed_work(system_unbound_wq, &kfence_timer, queue_delayed_work(system_unbound_wq, &kfence_timer,
msecs_to_jiffies(kfence_sample_interval)); msecs_to_jiffies(kfence_sample_interval));
} }
static DECLARE_DELAYED_WORK(kfence_timer, toggle_allocation_gate);
/* === Public interface ===================================================== */ /* === Public interface ===================================================== */
@ -809,8 +813,15 @@ static void kfence_init_enable(void)
{ {
if (!IS_ENABLED(CONFIG_KFENCE_STATIC_KEYS)) if (!IS_ENABLED(CONFIG_KFENCE_STATIC_KEYS))
static_branch_enable(&kfence_allocation_key); static_branch_enable(&kfence_allocation_key);
if (kfence_deferrable)
INIT_DEFERRABLE_WORK(&kfence_timer, toggle_allocation_gate);
else
INIT_DELAYED_WORK(&kfence_timer, toggle_allocation_gate);
WRITE_ONCE(kfence_enabled, true); WRITE_ONCE(kfence_enabled, true);
queue_delayed_work(system_unbound_wq, &kfence_timer, 0); queue_delayed_work(system_unbound_wq, &kfence_timer, 0);
pr_info("initialized - using %lu bytes for %d objects at 0x%p-0x%p\n", KFENCE_POOL_SIZE, pr_info("initialized - using %lu bytes for %d objects at 0x%p-0x%p\n", KFENCE_POOL_SIZE,
CONFIG_KFENCE_NUM_OBJECTS, (void *)__kfence_pool, CONFIG_KFENCE_NUM_OBJECTS, (void *)__kfence_pool,
(void *)(__kfence_pool + KFENCE_POOL_SIZE)); (void *)(__kfence_pool + KFENCE_POOL_SIZE));