https://github.com/torvalds/linux
Revision 86d32f9a7c54ad74f4514d7fef7c847883207291 authored by Vasily Averin on 14 April 2020, 20:33:16 UTC, committed by Linus Torvalds on 16 April 2020, 17:10:50 UTC
If seq_file .next function does not change position index,
read after some lseek can generate unexpected output:

    $ dd if=/proc/keys bs=1  # full usual output
    0f6bfdf5 I--Q---     2 perm 3f010000  1000  1000 user      4af2f79ab8848d0a: 740
    1fb91b32 I--Q---     3 perm 1f3f0000  1000 65534 keyring   _uid.1000: 2
    27589480 I--Q---     1 perm 0b0b0000     0     0 user      invocation_id: 16
    2f33ab67 I--Q---   152 perm 3f030000     0     0 keyring   _ses: 2
    33f1d8fa I--Q---     4 perm 3f030000  1000  1000 keyring   _ses: 1
    3d427fda I--Q---     2 perm 3f010000  1000  1000 user      69ec44aec7678e5a: 740
    3ead4096 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid_ses.1000: 1
    521+0 records in
    521+0 records out
    521 bytes copied, 0,00123769 s, 421 kB/s

But a read after lseek in middle of last line results in the partial
last line and then a repeat of the final line:

    $ dd if=/proc/keys bs=500 skip=1
    dd: /proc/keys: cannot skip to specified offset
    g   _uid_ses.1000: 1
    3ead4096 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid_ses.1000: 1
    0+1 records in
    0+1 records out
    97 bytes copied, 0,000135035 s, 718 kB/s

and a read after lseek beyond end of file results in the last line being
shown:

    $ dd if=/proc/keys bs=1000 skip=1   # read after lseek beyond end of file
    dd: /proc/keys: cannot skip to specified offset
    3ead4096 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid_ses.1000: 1
    0+1 records in
    0+1 records out
    76 bytes copied, 0,000119981 s, 633 kB/s

See https://bugzilla.kernel.org/show_bug.cgi?id=206283

Fixes: 1f4aace60b0e ("fs/seq_file.c: simplify seq_file iteration code ...")
Signed-off-by: Vasily Averin <vvs@virtuozzo.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Cc: stable@vger.kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 0008633
Raw File
Tip revision: 86d32f9a7c54ad74f4514d7fef7c847883207291 authored by Vasily Averin on 14 April 2020, 20:33:16 UTC
keys: Fix proc_keys_next to increase position index
Tip revision: 86d32f9
error-inject.c
// SPDX-License-Identifier: GPL-2.0
// error-inject.c: Function-level error injection table
#include <linux/error-injection.h>
#include <linux/debugfs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/slab.h>

/* Whitelist of symbols that can be overridden for error injection. */
static LIST_HEAD(error_injection_list);
static DEFINE_MUTEX(ei_mutex);
struct ei_entry {
	struct list_head list;
	unsigned long start_addr;
	unsigned long end_addr;
	int etype;
	void *priv;
};

bool within_error_injection_list(unsigned long addr)
{
	struct ei_entry *ent;
	bool ret = false;

	mutex_lock(&ei_mutex);
	list_for_each_entry(ent, &error_injection_list, list) {
		if (addr >= ent->start_addr && addr < ent->end_addr) {
			ret = true;
			break;
		}
	}
	mutex_unlock(&ei_mutex);
	return ret;
}

int get_injectable_error_type(unsigned long addr)
{
	struct ei_entry *ent;

	list_for_each_entry(ent, &error_injection_list, list) {
		if (addr >= ent->start_addr && addr < ent->end_addr)
			return ent->etype;
	}
	return EI_ETYPE_NONE;
}

/*
 * Lookup and populate the error_injection_list.
 *
 * For safety reasons we only allow certain functions to be overridden with
 * bpf_error_injection, so we need to populate the list of the symbols that have
 * been marked as safe for overriding.
 */
static void populate_error_injection_list(struct error_injection_entry *start,
					  struct error_injection_entry *end,
					  void *priv)
{
	struct error_injection_entry *iter;
	struct ei_entry *ent;
	unsigned long entry, offset = 0, size = 0;

	mutex_lock(&ei_mutex);
	for (iter = start; iter < end; iter++) {
		entry = arch_deref_entry_point((void *)iter->addr);

		if (!kernel_text_address(entry) ||
		    !kallsyms_lookup_size_offset(entry, &size, &offset)) {
			pr_err("Failed to find error inject entry at %p\n",
				(void *)entry);
			continue;
		}

		ent = kmalloc(sizeof(*ent), GFP_KERNEL);
		if (!ent)
			break;
		ent->start_addr = entry;
		ent->end_addr = entry + size;
		ent->etype = iter->etype;
		ent->priv = priv;
		INIT_LIST_HEAD(&ent->list);
		list_add_tail(&ent->list, &error_injection_list);
	}
	mutex_unlock(&ei_mutex);
}

/* Markers of the _error_inject_whitelist section */
extern struct error_injection_entry __start_error_injection_whitelist[];
extern struct error_injection_entry __stop_error_injection_whitelist[];

static void __init populate_kernel_ei_list(void)
{
	populate_error_injection_list(__start_error_injection_whitelist,
				      __stop_error_injection_whitelist,
				      NULL);
}

#ifdef CONFIG_MODULES
static void module_load_ei_list(struct module *mod)
{
	if (!mod->num_ei_funcs)
		return;

	populate_error_injection_list(mod->ei_funcs,
				      mod->ei_funcs + mod->num_ei_funcs, mod);
}

static void module_unload_ei_list(struct module *mod)
{
	struct ei_entry *ent, *n;

	if (!mod->num_ei_funcs)
		return;

	mutex_lock(&ei_mutex);
	list_for_each_entry_safe(ent, n, &error_injection_list, list) {
		if (ent->priv == mod) {
			list_del_init(&ent->list);
			kfree(ent);
		}
	}
	mutex_unlock(&ei_mutex);
}

/* Module notifier call back, checking error injection table on the module */
static int ei_module_callback(struct notifier_block *nb,
			      unsigned long val, void *data)
{
	struct module *mod = data;

	if (val == MODULE_STATE_COMING)
		module_load_ei_list(mod);
	else if (val == MODULE_STATE_GOING)
		module_unload_ei_list(mod);

	return NOTIFY_DONE;
}

static struct notifier_block ei_module_nb = {
	.notifier_call = ei_module_callback,
	.priority = 0
};

static __init int module_ei_init(void)
{
	return register_module_notifier(&ei_module_nb);
}
#else /* !CONFIG_MODULES */
#define module_ei_init()	(0)
#endif

/*
 * error_injection/whitelist -- shows which functions can be overridden for
 * error injection.
 */
static void *ei_seq_start(struct seq_file *m, loff_t *pos)
{
	mutex_lock(&ei_mutex);
	return seq_list_start(&error_injection_list, *pos);
}

static void ei_seq_stop(struct seq_file *m, void *v)
{
	mutex_unlock(&ei_mutex);
}

static void *ei_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
	return seq_list_next(v, &error_injection_list, pos);
}

static const char *error_type_string(int etype)
{
	switch (etype) {
	case EI_ETYPE_NULL:
		return "NULL";
	case EI_ETYPE_ERRNO:
		return "ERRNO";
	case EI_ETYPE_ERRNO_NULL:
		return "ERRNO_NULL";
	default:
		return "(unknown)";
	}
}

static int ei_seq_show(struct seq_file *m, void *v)
{
	struct ei_entry *ent = list_entry(v, struct ei_entry, list);

	seq_printf(m, "%ps\t%s\n", (void *)ent->start_addr,
		   error_type_string(ent->etype));
	return 0;
}

static const struct seq_operations ei_seq_ops = {
	.start = ei_seq_start,
	.next  = ei_seq_next,
	.stop  = ei_seq_stop,
	.show  = ei_seq_show,
};

static int ei_open(struct inode *inode, struct file *filp)
{
	return seq_open(filp, &ei_seq_ops);
}

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

static int __init ei_debugfs_init(void)
{
	struct dentry *dir, *file;

	dir = debugfs_create_dir("error_injection", NULL);
	if (!dir)
		return -ENOMEM;

	file = debugfs_create_file("list", 0444, dir, NULL, &debugfs_ei_ops);
	if (!file) {
		debugfs_remove(dir);
		return -ENOMEM;
	}

	return 0;
}

static int __init init_error_injection(void)
{
	populate_kernel_ei_list();

	if (!module_ei_init())
		ei_debugfs_init();

	return 0;
}
late_initcall(init_error_injection);
back to top