root/net/core/skb_fault_injection.c
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/debugfs.h>
#include <linux/fault-inject.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>

static struct {
        struct fault_attr attr;
        char devname[IFNAMSIZ];
        bool filtered;
} skb_realloc = {
        .attr = FAULT_ATTR_INITIALIZER,
        .filtered = false,
};

static bool should_fail_net_realloc_skb(struct sk_buff *skb)
{
        struct net_device *net = skb->dev;

        if (skb_realloc.filtered &&
            strncmp(net->name, skb_realloc.devname, IFNAMSIZ))
                /* device name filter set, but names do not match */
                return false;

        if (!should_fail(&skb_realloc.attr, 1))
                return false;

        return true;
}
ALLOW_ERROR_INJECTION(should_fail_net_realloc_skb, TRUE);

void skb_might_realloc(struct sk_buff *skb)
{
        if (!should_fail_net_realloc_skb(skb))
                return;

        pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
}
EXPORT_SYMBOL(skb_might_realloc);

static int __init fail_skb_realloc_setup(char *str)
{
        return setup_fault_attr(&skb_realloc.attr, str);
}
__setup("fail_skb_realloc=", fail_skb_realloc_setup);

static void reset_settings(void)
{
        skb_realloc.filtered = false;
        memset(&skb_realloc.devname, 0, IFNAMSIZ);
}

static ssize_t devname_write(struct file *file, const char __user *buffer,
                             size_t count, loff_t *ppos)
{
        ssize_t ret;

        reset_settings();
        ret = simple_write_to_buffer(&skb_realloc.devname, IFNAMSIZ,
                                     ppos, buffer, count);
        if (ret < 0)
                return ret;

        skb_realloc.devname[IFNAMSIZ - 1] = '\0';
        /* Remove a possible \n at the end of devname */
        strim(skb_realloc.devname);

        if (strnlen(skb_realloc.devname, IFNAMSIZ))
                skb_realloc.filtered = true;

        return count;
}

static ssize_t devname_read(struct file *file,
                            char __user *buffer,
                            size_t size, loff_t *ppos)
{
        if (!skb_realloc.filtered)
                return 0;

        return simple_read_from_buffer(buffer, size, ppos, &skb_realloc.devname,
                                       strlen(skb_realloc.devname));
}

static const struct file_operations devname_ops = {
        .write = devname_write,
        .read = devname_read,
};

static int __init fail_skb_realloc_debugfs(void)
{
        umode_t mode = S_IFREG | 0600;
        struct dentry *dir;

        dir = fault_create_debugfs_attr("fail_skb_realloc", NULL,
                                        &skb_realloc.attr);
        if (IS_ERR(dir))
                return PTR_ERR(dir);

        debugfs_create_file("devname", mode, dir, NULL, &devname_ops);

        return 0;
}

late_initcall(fail_skb_realloc_debugfs);