root/kernel/trace/rv/rv_reactors.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
 *
 * Runtime reactor interface.
 *
 * A runtime monitor can cause a reaction to the detection of an
 * exception on the model's execution. By default, the monitors have
 * tracing reactions, printing the monitor output via tracepoints.
 * But other reactions can be added (on-demand) via this interface.
 *
 * == Registering reactors ==
 *
 * The struct rv_reactor defines a callback function to be executed
 * in case of a model exception happens. The callback function
 * receives a message to be (optionally) printed before executing
 * the reaction.
 *
 * A RV reactor is registered via:
 *   int rv_register_reactor(struct rv_reactor *reactor)
 * And unregistered via:
 *   int rv_unregister_reactor(struct rv_reactor *reactor)
 *
 * These functions are exported to modules, enabling reactors to be
 * dynamically loaded.
 *
 * == User interface ==
 *
 * The user interface resembles the kernel tracing interface and
 * presents these files:
 *
 *  "available_reactors"
 *    - List the available reactors, one per line.
 *
 *    For example:
 *      # cat available_reactors
 *      nop
 *      panic
 *      printk
 *
 *  "reacting_on"
 *    - It is an on/off general switch for reactors, disabling
 *    all reactions.
 *
 *  "monitors/MONITOR/reactors"
 *    - List available reactors, with the select reaction for the given
 *    MONITOR inside []. The default one is the nop (no operation)
 *    reactor.
 *    - Writing the name of an reactor enables it to the given
 *    MONITOR.
 *
 *    For example:
 *      # cat monitors/wip/reactors
 *      [nop]
 *      panic
 *      printk
 *      # echo panic > monitors/wip/reactors
 *      # cat monitors/wip/reactors
 *      nop
 *      [panic]
 *      printk
 */

#include <linux/lockdep.h>
#include <linux/slab.h>

#include "rv.h"

/*
 * Interface for the reactor register.
 */
static LIST_HEAD(rv_reactors_list);

static struct rv_reactor *get_reactor_rdef_by_name(char *name)
{
        struct rv_reactor *r;

        list_for_each_entry(r, &rv_reactors_list, list) {
                if (strcmp(name, r->name) == 0)
                        return r;
        }
        return NULL;
}

/*
 * Available reactors seq functions.
 */
static int reactors_show(struct seq_file *m, void *p)
{
        struct rv_reactor *reactor = container_of(p, struct rv_reactor, list);

        seq_printf(m, "%s\n", reactor->name);
        return 0;
}

static void reactors_stop(struct seq_file *m, void *p)
{
        mutex_unlock(&rv_interface_lock);
}

static void *reactors_start(struct seq_file *m, loff_t *pos)
{
        mutex_lock(&rv_interface_lock);
        return seq_list_start(&rv_reactors_list, *pos);
}

static void *reactors_next(struct seq_file *m, void *p, loff_t *pos)
{
        return seq_list_next(p, &rv_reactors_list, pos);
}

/*
 * available_reactors seq definition.
 */
static const struct seq_operations available_reactors_seq_ops = {
        .start  = reactors_start,
        .next   = reactors_next,
        .stop   = reactors_stop,
        .show   = reactors_show
};

/*
 * available_reactors interface.
 */
static int available_reactors_open(struct inode *inode, struct file *file)
{
        return seq_open(file, &available_reactors_seq_ops);
};

static const struct file_operations available_reactors_ops = {
        .open    = available_reactors_open,
        .read    = seq_read,
        .llseek  = seq_lseek,
        .release = seq_release
};

/*
 * Monitor's reactor file.
 */
static int monitor_reactor_show(struct seq_file *m, void *p)
{
        struct rv_monitor *mon = m->private;
        struct rv_reactor *reactor = container_of(p, struct rv_reactor, list);

        if (mon->reactor == reactor)
                seq_printf(m, "[%s]\n", reactor->name);
        else
                seq_printf(m, "%s\n", reactor->name);
        return 0;
}

/*
 * available_reactors seq definition.
 */
static const struct seq_operations monitor_reactors_seq_ops = {
        .start  = reactors_start,
        .next   = reactors_next,
        .stop   = reactors_stop,
        .show   = monitor_reactor_show
};

static void monitor_swap_reactors_single(struct rv_monitor *mon,
                                         struct rv_reactor *reactor,
                                         bool nested)
{
        bool monitor_enabled;

        /* nothing to do */
        if (mon->reactor == reactor)
                return;

        monitor_enabled = mon->enabled;
        if (monitor_enabled)
                rv_disable_monitor(mon);

        mon->reactor = reactor;
        mon->react = reactor->react;

        /* enable only once if iterating through a container */
        if (monitor_enabled && !nested)
                rv_enable_monitor(mon);
}

static void monitor_swap_reactors(struct rv_monitor *mon, struct rv_reactor *reactor)
{
        struct rv_monitor *p = mon;

        if (rv_is_container_monitor(mon))
                list_for_each_entry_continue(p, &rv_monitors_list, list) {
                        if (p->parent != mon)
                                break;
                        monitor_swap_reactors_single(p, reactor, true);
                }
        /*
         * This call enables and disables the monitor if they were active.
         * In case of a container, we already disabled all and will enable all.
         * All nested monitors are enabled also if they were off, we may refine
         * this logic in the future.
         */
        monitor_swap_reactors_single(mon, reactor, false);
}

static ssize_t
monitor_reactors_write(struct file *file, const char __user *user_buf,
                      size_t count, loff_t *ppos)
{
        char buff[MAX_RV_REACTOR_NAME_SIZE + 2];
        struct rv_monitor *mon;
        struct rv_reactor *reactor;
        struct seq_file *seq_f;
        int retval = -EINVAL;
        char *ptr;
        int len;

        if (count < 1 || count > MAX_RV_REACTOR_NAME_SIZE + 1)
                return -EINVAL;

        memset(buff, 0, sizeof(buff));

        retval = simple_write_to_buffer(buff, sizeof(buff) - 1, ppos, user_buf, count);
        if (retval < 0)
                return -EFAULT;

        ptr = strim(buff);

        len = strlen(ptr);
        if (!len)
                return count;

        /*
         * See monitor_reactors_open()
         */
        seq_f = file->private_data;
        mon = seq_f->private;

        guard(mutex)(&rv_interface_lock);

        list_for_each_entry(reactor, &rv_reactors_list, list) {
                if (strcmp(ptr, reactor->name) != 0)
                        continue;

                monitor_swap_reactors(mon, reactor);

                return count;
        }

        return -EINVAL;
}

/*
 * available_reactors interface.
 */
static int monitor_reactors_open(struct inode *inode, struct file *file)
{
        struct rv_monitor *mon = inode->i_private;
        struct seq_file *seq_f;
        int ret;

        ret = seq_open(file, &monitor_reactors_seq_ops);
        if (ret < 0)
                return ret;

        /*
         * seq_open stores the seq_file on the file->private data.
         */
        seq_f = file->private_data;

        /*
         * Copy the create file "private" data to the seq_file private data.
         */
        seq_f->private = mon;

        return 0;
};

static const struct file_operations monitor_reactors_ops = {
        .open    = monitor_reactors_open,
        .read    = seq_read,
        .llseek  = seq_lseek,
        .release = seq_release,
        .write = monitor_reactors_write
};

static int __rv_register_reactor(struct rv_reactor *reactor)
{
        struct rv_reactor *r;

        list_for_each_entry(r, &rv_reactors_list, list) {
                if (strcmp(reactor->name, r->name) == 0) {
                        pr_info("Reactor %s is already registered\n", reactor->name);
                        return -EINVAL;
                }
        }

        list_add_tail(&reactor->list, &rv_reactors_list);

        return 0;
}

/**
 * rv_register_reactor - register a rv reactor.
 * @reactor:    The rv_reactor to be registered.
 *
 * Returns 0 if successful, error otherwise.
 */
int rv_register_reactor(struct rv_reactor *reactor)
{
        if (strlen(reactor->name) >= MAX_RV_REACTOR_NAME_SIZE) {
                pr_info("Reactor %s has a name longer than %d\n",
                        reactor->name, MAX_RV_MONITOR_NAME_SIZE);
                return -EINVAL;
        }

        guard(mutex)(&rv_interface_lock);
        return __rv_register_reactor(reactor);
}

/**
 * rv_unregister_reactor - unregister a rv reactor.
 * @reactor:    The rv_reactor to be unregistered.
 *
 * Returns 0 if successful, error otherwise.
 */
int rv_unregister_reactor(struct rv_reactor *reactor)
{
        guard(mutex)(&rv_interface_lock);
        list_del(&reactor->list);
        return 0;
}

/*
 * reacting_on interface.
 */
static bool __read_mostly reacting_on;

/**
 * rv_reacting_on - checks if reacting is on
 *
 * Returns 1 if on, 0 otherwise.
 */
static bool rv_reacting_on(void)
{
        /* Ensures that concurrent monitors read consistent reacting_on */
        smp_rmb();
        return READ_ONCE(reacting_on);
}

static ssize_t reacting_on_read_data(struct file *filp,
                                     char __user *user_buf,
                                     size_t count, loff_t *ppos)
{
        char *buff;

        buff = rv_reacting_on() ? "1\n" : "0\n";

        return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff)+1);
}

static void turn_reacting_off(void)
{
        WRITE_ONCE(reacting_on, false);
        /* Ensures that concurrent monitors read consistent reacting_on */
        smp_wmb();
}

static void turn_reacting_on(void)
{
        WRITE_ONCE(reacting_on, true);
        /* Ensures that concurrent monitors read consistent reacting_on */
        smp_wmb();
}

static ssize_t reacting_on_write_data(struct file *filp, const char __user *user_buf,
                                      size_t count, loff_t *ppos)
{
        int retval;
        bool val;

        retval = kstrtobool_from_user(user_buf, count, &val);
        if (retval)
                return retval;

        guard(mutex)(&rv_interface_lock);

        if (val)
                turn_reacting_on();
        else
                turn_reacting_off();

        /*
         * Wait for the execution of all events to finish
         * before returning to user-space.
         */
        tracepoint_synchronize_unregister();

        return count;
}

static const struct file_operations reacting_on_fops = {
        .open   = simple_open,
        .write  = reacting_on_write_data,
        .read   = reacting_on_read_data,
};

/**
 * reactor_populate_monitor - creates per monitor reactors file
 * @mon:        The monitor.
 * @root:       The directory of the monitor.
 *
 * Returns 0 if successful, error otherwise.
 */
int reactor_populate_monitor(struct rv_monitor *mon, struct dentry *root)
{
        struct dentry *tmp;

        tmp = rv_create_file("reactors", RV_MODE_WRITE, root, mon, &monitor_reactors_ops);
        if (!tmp)
                return -ENOMEM;

        /*
         * Configure as the rv_nop reactor.
         */
        mon->reactor = get_reactor_rdef_by_name("nop");

        return 0;
}

/*
 * Nop reactor register
 */
__printf(1, 0) static void rv_nop_reaction(const char *msg, va_list args)
{
}

static struct rv_reactor rv_nop = {
        .name = "nop",
        .description = "no-operation reactor: do nothing.",
        .react = rv_nop_reaction
};

int init_rv_reactors(struct dentry *root_dir)
{
        int retval;

        struct dentry *available __free(rv_remove) =
                rv_create_file("available_reactors", RV_MODE_READ, root_dir,
                                NULL, &available_reactors_ops);

        struct dentry *reacting __free(rv_remove) =
                rv_create_file("reacting_on", RV_MODE_WRITE, root_dir, NULL, &reacting_on_fops);

        if (!reacting || !available)
                return -ENOMEM;

        retval = __rv_register_reactor(&rv_nop);
        if (retval)
                return retval;

        turn_reacting_on();

        retain_and_null_ptr(available);
        retain_and_null_ptr(reacting);
        return 0;
}

void rv_react(struct rv_monitor *monitor, const char *msg, ...)
{
        static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE);
        va_list args;

        if (!rv_reacting_on() || !monitor->react)
                return;

        va_start(args, msg);

        lock_map_acquire_try(&rv_react_map);
        monitor->react(msg, args);
        lock_map_release(&rv_react_map);

        va_end(args);
}