linux/samples/livepatch/livepatch-shadow-mod.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2017 Joe Lawrence <joe.lawrence@redhat.com>
   4 */
   5
   6/*
   7 * livepatch-shadow-mod.c - Shadow variables, buggy module demo
   8 *
   9 * Purpose
  10 * -------
  11 *
  12 * As a demonstration of livepatch shadow variable API, this module
  13 * introduces memory leak behavior that livepatch modules
  14 * livepatch-shadow-fix1.ko and livepatch-shadow-fix2.ko correct and
  15 * enhance.
  16 *
  17 * WARNING - even though the livepatch-shadow-fix modules patch the
  18 * memory leak, please load these modules at your own risk -- some
  19 * amount of memory may leaked before the bug is patched.
  20 *
  21 *
  22 * Usage
  23 * -----
  24 *
  25 * Step 1 - Load the buggy demonstration module:
  26 *
  27 *   insmod samples/livepatch/livepatch-shadow-mod.ko
  28 *
  29 * Watch dmesg output for a few moments to see new dummy being allocated
  30 * and a periodic cleanup check.  (Note: a small amount of memory is
  31 * being leaked.)
  32 *
  33 *
  34 * Step 2 - Load livepatch fix1:
  35 *
  36 *   insmod samples/livepatch/livepatch-shadow-fix1.ko
  37 *
  38 * Continue watching dmesg and note that now livepatch_fix1_dummy_free()
  39 * and livepatch_fix1_dummy_alloc() are logging messages about leaked
  40 * memory and eventually leaks prevented.
  41 *
  42 *
  43 * Step 3 - Load livepatch fix2 (on top of fix1):
  44 *
  45 *   insmod samples/livepatch/livepatch-shadow-fix2.ko
  46 *
  47 * This module extends functionality through shadow variables, as a new
  48 * "check" counter is added to the dummy structure.  Periodic dmesg
  49 * messages will log these as dummies are cleaned up.
  50 *
  51 *
  52 * Step 4 - Cleanup
  53 *
  54 * Unwind the demonstration by disabling the livepatch fix modules, then
  55 * removing them and the demo module:
  56 *
  57 *   echo 0 > /sys/kernel/livepatch/livepatch_shadow_fix2/enabled
  58 *   echo 0 > /sys/kernel/livepatch/livepatch_shadow_fix1/enabled
  59 *   rmmod livepatch-shadow-fix2
  60 *   rmmod livepatch-shadow-fix1
  61 *   rmmod livepatch-shadow-mod
  62 */
  63
  64
  65#include <linux/kernel.h>
  66#include <linux/module.h>
  67#include <linux/sched.h>
  68#include <linux/slab.h>
  69#include <linux/stat.h>
  70#include <linux/workqueue.h>
  71
  72MODULE_LICENSE("GPL");
  73MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
  74MODULE_DESCRIPTION("Buggy module for shadow variable demo");
  75
  76/* Allocate new dummies every second */
  77#define ALLOC_PERIOD    1
  78/* Check for expired dummies after a few new ones have been allocated */
  79#define CLEANUP_PERIOD  (3 * ALLOC_PERIOD)
  80/* Dummies expire after a few cleanup instances */
  81#define EXPIRE_PERIOD   (4 * CLEANUP_PERIOD)
  82
  83/*
  84 * Keep a list of all the dummies so we can clean up any residual ones
  85 * on module exit
  86 */
  87static LIST_HEAD(dummy_list);
  88static DEFINE_MUTEX(dummy_list_mutex);
  89
  90struct dummy {
  91        struct list_head list;
  92        unsigned long jiffies_expire;
  93};
  94
  95static __used noinline struct dummy *dummy_alloc(void)
  96{
  97        struct dummy *d;
  98        void *leak;
  99
 100        d = kzalloc(sizeof(*d), GFP_KERNEL);
 101        if (!d)
 102                return NULL;
 103
 104        d->jiffies_expire = jiffies +
 105                msecs_to_jiffies(1000 * EXPIRE_PERIOD);
 106
 107        /* Oops, forgot to save leak! */
 108        leak = kzalloc(sizeof(int), GFP_KERNEL);
 109        if (!leak) {
 110                kfree(d);
 111                return NULL;
 112        }
 113
 114        pr_info("%s: dummy @ %p, expires @ %lx\n",
 115                __func__, d, d->jiffies_expire);
 116
 117        return d;
 118}
 119
 120static __used noinline void dummy_free(struct dummy *d)
 121{
 122        pr_info("%s: dummy @ %p, expired = %lx\n",
 123                __func__, d, d->jiffies_expire);
 124
 125        kfree(d);
 126}
 127
 128static __used noinline bool dummy_check(struct dummy *d,
 129                                           unsigned long jiffies)
 130{
 131        return time_after(jiffies, d->jiffies_expire);
 132}
 133
 134/*
 135 * alloc_work_func: allocates new dummy structures, allocates additional
 136 *                  memory, aptly named "leak", but doesn't keep
 137 *                  permanent record of it.
 138 */
 139
 140static void alloc_work_func(struct work_struct *work);
 141static DECLARE_DELAYED_WORK(alloc_dwork, alloc_work_func);
 142
 143static void alloc_work_func(struct work_struct *work)
 144{
 145        struct dummy *d;
 146
 147        d = dummy_alloc();
 148        if (!d)
 149                return;
 150
 151        mutex_lock(&dummy_list_mutex);
 152        list_add(&d->list, &dummy_list);
 153        mutex_unlock(&dummy_list_mutex);
 154
 155        schedule_delayed_work(&alloc_dwork,
 156                msecs_to_jiffies(1000 * ALLOC_PERIOD));
 157}
 158
 159/*
 160 * cleanup_work_func: frees dummy structures.  Without knownledge of
 161 *                    "leak", it leaks the additional memory that
 162 *                    alloc_work_func created.
 163 */
 164
 165static void cleanup_work_func(struct work_struct *work);
 166static DECLARE_DELAYED_WORK(cleanup_dwork, cleanup_work_func);
 167
 168static void cleanup_work_func(struct work_struct *work)
 169{
 170        struct dummy *d, *tmp;
 171        unsigned long j;
 172
 173        j = jiffies;
 174        pr_info("%s: jiffies = %lx\n", __func__, j);
 175
 176        mutex_lock(&dummy_list_mutex);
 177        list_for_each_entry_safe(d, tmp, &dummy_list, list) {
 178
 179                /* Kick out and free any expired dummies */
 180                if (dummy_check(d, j)) {
 181                        list_del(&d->list);
 182                        dummy_free(d);
 183                }
 184        }
 185        mutex_unlock(&dummy_list_mutex);
 186
 187        schedule_delayed_work(&cleanup_dwork,
 188                msecs_to_jiffies(1000 * CLEANUP_PERIOD));
 189}
 190
 191static int livepatch_shadow_mod_init(void)
 192{
 193        schedule_delayed_work(&alloc_dwork,
 194                msecs_to_jiffies(1000 * ALLOC_PERIOD));
 195        schedule_delayed_work(&cleanup_dwork,
 196                msecs_to_jiffies(1000 * CLEANUP_PERIOD));
 197
 198        return 0;
 199}
 200
 201static void livepatch_shadow_mod_exit(void)
 202{
 203        struct dummy *d, *tmp;
 204
 205        /* Wait for any dummies at work */
 206        cancel_delayed_work_sync(&alloc_dwork);
 207        cancel_delayed_work_sync(&cleanup_dwork);
 208
 209        /* Cleanup residual dummies */
 210        list_for_each_entry_safe(d, tmp, &dummy_list, list) {
 211                list_del(&d->list);
 212                dummy_free(d);
 213        }
 214}
 215
 216module_init(livepatch_shadow_mod_init);
 217module_exit(livepatch_shadow_mod_exit);
 218