162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2017 Joe Lawrence <joe.lawrence@redhat.com> 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci/* 762306a36Sopenharmony_ci * livepatch-shadow-mod.c - Shadow variables, buggy module demo 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Purpose 1062306a36Sopenharmony_ci * ------- 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * As a demonstration of livepatch shadow variable API, this module 1362306a36Sopenharmony_ci * introduces memory leak behavior that livepatch modules 1462306a36Sopenharmony_ci * livepatch-shadow-fix1.ko and livepatch-shadow-fix2.ko correct and 1562306a36Sopenharmony_ci * enhance. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * WARNING - even though the livepatch-shadow-fix modules patch the 1862306a36Sopenharmony_ci * memory leak, please load these modules at your own risk -- some 1962306a36Sopenharmony_ci * amount of memory may leaked before the bug is patched. 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * Usage 2362306a36Sopenharmony_ci * ----- 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * Step 1 - Load the buggy demonstration module: 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * insmod samples/livepatch/livepatch-shadow-mod.ko 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * Watch dmesg output for a few moments to see new dummy being allocated 3062306a36Sopenharmony_ci * and a periodic cleanup check. (Note: a small amount of memory is 3162306a36Sopenharmony_ci * being leaked.) 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * Step 2 - Load livepatch fix1: 3562306a36Sopenharmony_ci * 3662306a36Sopenharmony_ci * insmod samples/livepatch/livepatch-shadow-fix1.ko 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * Continue watching dmesg and note that now livepatch_fix1_dummy_free() 3962306a36Sopenharmony_ci * and livepatch_fix1_dummy_alloc() are logging messages about leaked 4062306a36Sopenharmony_ci * memory and eventually leaks prevented. 4162306a36Sopenharmony_ci * 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * Step 3 - Load livepatch fix2 (on top of fix1): 4462306a36Sopenharmony_ci * 4562306a36Sopenharmony_ci * insmod samples/livepatch/livepatch-shadow-fix2.ko 4662306a36Sopenharmony_ci * 4762306a36Sopenharmony_ci * This module extends functionality through shadow variables, as a new 4862306a36Sopenharmony_ci * "check" counter is added to the dummy structure. Periodic dmesg 4962306a36Sopenharmony_ci * messages will log these as dummies are cleaned up. 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * 5262306a36Sopenharmony_ci * Step 4 - Cleanup 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * Unwind the demonstration by disabling the livepatch fix modules, then 5562306a36Sopenharmony_ci * removing them and the demo module: 5662306a36Sopenharmony_ci * 5762306a36Sopenharmony_ci * echo 0 > /sys/kernel/livepatch/livepatch_shadow_fix2/enabled 5862306a36Sopenharmony_ci * echo 0 > /sys/kernel/livepatch/livepatch_shadow_fix1/enabled 5962306a36Sopenharmony_ci * rmmod livepatch-shadow-fix2 6062306a36Sopenharmony_ci * rmmod livepatch-shadow-fix1 6162306a36Sopenharmony_ci * rmmod livepatch-shadow-mod 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#include <linux/kernel.h> 6662306a36Sopenharmony_ci#include <linux/module.h> 6762306a36Sopenharmony_ci#include <linux/sched.h> 6862306a36Sopenharmony_ci#include <linux/slab.h> 6962306a36Sopenharmony_ci#include <linux/stat.h> 7062306a36Sopenharmony_ci#include <linux/workqueue.h> 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 7362306a36Sopenharmony_ciMODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>"); 7462306a36Sopenharmony_ciMODULE_DESCRIPTION("Buggy module for shadow variable demo"); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Allocate new dummies every second */ 7762306a36Sopenharmony_ci#define ALLOC_PERIOD 1 7862306a36Sopenharmony_ci/* Check for expired dummies after a few new ones have been allocated */ 7962306a36Sopenharmony_ci#define CLEANUP_PERIOD (3 * ALLOC_PERIOD) 8062306a36Sopenharmony_ci/* Dummies expire after a few cleanup instances */ 8162306a36Sopenharmony_ci#define EXPIRE_PERIOD (4 * CLEANUP_PERIOD) 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci/* 8462306a36Sopenharmony_ci * Keep a list of all the dummies so we can clean up any residual ones 8562306a36Sopenharmony_ci * on module exit 8662306a36Sopenharmony_ci */ 8762306a36Sopenharmony_cistatic LIST_HEAD(dummy_list); 8862306a36Sopenharmony_cistatic DEFINE_MUTEX(dummy_list_mutex); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistruct dummy { 9162306a36Sopenharmony_ci struct list_head list; 9262306a36Sopenharmony_ci unsigned long jiffies_expire; 9362306a36Sopenharmony_ci}; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic __used noinline struct dummy *dummy_alloc(void) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct dummy *d; 9862306a36Sopenharmony_ci int *leak; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci d = kzalloc(sizeof(*d), GFP_KERNEL); 10162306a36Sopenharmony_ci if (!d) 10262306a36Sopenharmony_ci return NULL; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci d->jiffies_expire = jiffies + 10562306a36Sopenharmony_ci msecs_to_jiffies(1000 * EXPIRE_PERIOD); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* Oops, forgot to save leak! */ 10862306a36Sopenharmony_ci leak = kzalloc(sizeof(*leak), GFP_KERNEL); 10962306a36Sopenharmony_ci if (!leak) { 11062306a36Sopenharmony_ci kfree(d); 11162306a36Sopenharmony_ci return NULL; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci pr_info("%s: dummy @ %p, expires @ %lx\n", 11562306a36Sopenharmony_ci __func__, d, d->jiffies_expire); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return d; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic __used noinline void dummy_free(struct dummy *d) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci pr_info("%s: dummy @ %p, expired = %lx\n", 12362306a36Sopenharmony_ci __func__, d, d->jiffies_expire); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci kfree(d); 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic __used noinline bool dummy_check(struct dummy *d, 12962306a36Sopenharmony_ci unsigned long jiffies) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci return time_after(jiffies, d->jiffies_expire); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci/* 13562306a36Sopenharmony_ci * alloc_work_func: allocates new dummy structures, allocates additional 13662306a36Sopenharmony_ci * memory, aptly named "leak", but doesn't keep 13762306a36Sopenharmony_ci * permanent record of it. 13862306a36Sopenharmony_ci */ 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void alloc_work_func(struct work_struct *work); 14162306a36Sopenharmony_cistatic DECLARE_DELAYED_WORK(alloc_dwork, alloc_work_func); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic void alloc_work_func(struct work_struct *work) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct dummy *d; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci d = dummy_alloc(); 14862306a36Sopenharmony_ci if (!d) 14962306a36Sopenharmony_ci return; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci mutex_lock(&dummy_list_mutex); 15262306a36Sopenharmony_ci list_add(&d->list, &dummy_list); 15362306a36Sopenharmony_ci mutex_unlock(&dummy_list_mutex); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci schedule_delayed_work(&alloc_dwork, 15662306a36Sopenharmony_ci msecs_to_jiffies(1000 * ALLOC_PERIOD)); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci/* 16062306a36Sopenharmony_ci * cleanup_work_func: frees dummy structures. Without knownledge of 16162306a36Sopenharmony_ci * "leak", it leaks the additional memory that 16262306a36Sopenharmony_ci * alloc_work_func created. 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic void cleanup_work_func(struct work_struct *work); 16662306a36Sopenharmony_cistatic DECLARE_DELAYED_WORK(cleanup_dwork, cleanup_work_func); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic void cleanup_work_func(struct work_struct *work) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci struct dummy *d, *tmp; 17162306a36Sopenharmony_ci unsigned long j; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci j = jiffies; 17462306a36Sopenharmony_ci pr_info("%s: jiffies = %lx\n", __func__, j); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci mutex_lock(&dummy_list_mutex); 17762306a36Sopenharmony_ci list_for_each_entry_safe(d, tmp, &dummy_list, list) { 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* Kick out and free any expired dummies */ 18062306a36Sopenharmony_ci if (dummy_check(d, j)) { 18162306a36Sopenharmony_ci list_del(&d->list); 18262306a36Sopenharmony_ci dummy_free(d); 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci mutex_unlock(&dummy_list_mutex); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci schedule_delayed_work(&cleanup_dwork, 18862306a36Sopenharmony_ci msecs_to_jiffies(1000 * CLEANUP_PERIOD)); 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic int livepatch_shadow_mod_init(void) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci schedule_delayed_work(&alloc_dwork, 19462306a36Sopenharmony_ci msecs_to_jiffies(1000 * ALLOC_PERIOD)); 19562306a36Sopenharmony_ci schedule_delayed_work(&cleanup_dwork, 19662306a36Sopenharmony_ci msecs_to_jiffies(1000 * CLEANUP_PERIOD)); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic void livepatch_shadow_mod_exit(void) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct dummy *d, *tmp; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci /* Wait for any dummies at work */ 20662306a36Sopenharmony_ci cancel_delayed_work_sync(&alloc_dwork); 20762306a36Sopenharmony_ci cancel_delayed_work_sync(&cleanup_dwork); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* Cleanup residual dummies */ 21062306a36Sopenharmony_ci list_for_each_entry_safe(d, tmp, &dummy_list, list) { 21162306a36Sopenharmony_ci list_del(&d->list); 21262306a36Sopenharmony_ci dummy_free(d); 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cimodule_init(livepatch_shadow_mod_init); 21762306a36Sopenharmony_cimodule_exit(livepatch_shadow_mod_exit); 218