CVE-2011-1021 (Other, published 2012-06-21T23:55Z)
Description: value: drivers/acpi/debugfs.c in the Linux kernel before 3.0 allows local users to modify arbitrary kernel memory locations by leveraging root privileges to write to the /sys/kernel/debug/acpi/custom_method file. NOTE: this vulnerability exists because of an incomplete fix for CVE-2010-4347.
Severity: LOW (CVSS v3 base score: )
Weakness: CWE-264 \u2014 Permissions, Privileges, and Access Controls
Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Repository: https://github.com/torvalds/linux
Files changed:
Documentation/acpi/method-customizing.txt
drivers/acpi/Kconfig
drivers/acpi/Makefile
drivers/acpi/custom_method.c
drivers/acpi/debugfs.c
Commit message: ACPI: Split out custom_method functionality into an own driver

With /sys/kernel/debug/acpi/custom_method root can write
to arbitrary memory and increase his priveleges, even if
these are restricted.

-> Make this an own debug .config option and warn about the
security issue in the config description.

-> Still keep acpi/debugfs.c which now only creates an empty
   /sys/kernel/debug/acpi directory. There might be other
   users of it later.

Signed-off-by: Thomas Renninger <trenn@suse.de>
Acked-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: rui.zhang@intel.com
Signed-off-by: Len Brown <len.brown@intel.com>


Vulnerable code:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
static struct dentry *cm_dentry;

/* /sys/kernel/debug/acpi/custom_method */

static ssize_t cm_write(struct file *file, const char __user * user_buf,
			size_t count, loff_t *ppos)
{
	static char *buf;
	static u32 max_size;
	static u32 uncopied_bytes;

	struct acpi_table_header table;
	acpi_status status;

	if (!(*ppos)) {
		/* parse the table header to get the table length */
		if (count <= sizeof(struct acpi_table_header))
			return -EINVAL;
		if (copy_from_user(&table, user_buf,
				   sizeof(struct acpi_table_header)))
			return -EFAULT;
		uncopied_bytes = max_size = table.length;
		buf = kzalloc(max_size, GFP_KERNEL);
		if (!buf)
			return -ENOMEM;
	}

	if (buf == NULL)
		return -EINVAL;

	if ((*ppos > max_size) ||
	    (*ppos + count > max_size) ||
	    (*ppos + count < count) ||
	    (count > uncopied_bytes))
		return -EINVAL;

	if (copy_from_user(buf + (*ppos), user_buf, count)) {
		kfree(buf);
		buf = NULL;
		return -EFAULT;
	}

	uncopied_bytes -= count;
	*ppos += count;

	if (!uncopied_bytes) {
		status = acpi_install_method(buf);
		kfree(buf);
		buf = NULL;
		if (ACPI_FAILURE(status))
			return -EINVAL;
		add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
	}

	return count;
}

static const struct file_operations cm_fops = {
	.write = cm_write,
	.llseek = default_llseek,
};

static int __init acpi_custom_method_init(void)
{
	if (!acpi_debugfs_dir)
		return -ENOENT;

	cm_dentry = debugfs_create_file("custom_method", S_IWUSR,
					acpi_debugfs_dir, NULL, &cm_fops);
	if (!cm_dentry)
		return -ENODEV;

	return 0;
}

	acpi_custom_method_init();

Fixed code:

Note: Be aware that root can mis-use this driver to modify arbitrary
      memory and gain additional rights, if root's privileges got
      restricted (for example if root is not allowed to load additional
      modules after boot).
config ACPI_CUSTOM_METHOD
	tristate "Allow ACPI methods to be inserted/replaced at run time"
	depends on DEBUG_FS
	default n
	help
	  This debug facility allows ACPI AML methods to me inserted and/or
	  replaced without rebooting the system. For details refer to:
	  Documentation/acpi/method-customizing.txt.

	  NOTE: This option is security sensitive, because it allows arbitrary
	  kernel memory to be written to by root (uid=0) users, allowing them
	  to bypass certain security measures (e.g. if root is not allowed to
	  load additional kernel modules after boot, this feature may be used
	  to override that restriction).

obj-$(CONFIG_ACPI_CUSTOM_METHOD)+= custom_method.o
/*
 * debugfs.c - ACPI debugfs interface to userspace.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <acpi/acpi_drivers.h>

#include "internal.h"

#define _COMPONENT		ACPI_SYSTEM_COMPONENT
ACPI_MODULE_NAME("custom_method");
MODULE_LICENSE("GPL");

static struct dentry *cm_dentry;

/* /sys/kernel/debug/acpi/custom_method */

static ssize_t cm_write(struct file *file, const char __user * user_buf,
			size_t count, loff_t *ppos)
{
	static char *buf;
	static u32 max_size;
	static u32 uncopied_bytes;

	struct acpi_table_header table;
	acpi_status status;

	if (!(*ppos)) {
		/* parse the table header to get the table length */
		if (count <= sizeof(struct acpi_table_header))
			return -EINVAL;
		if (copy_from_user(&table, user_buf,
				   sizeof(struct acpi_table_header)))
			return -EFAULT;
		uncopied_bytes = max_size = table.length;
		buf = kzalloc(max_size, GFP_KERNEL);
		if (!buf)
			return -ENOMEM;
	}

	if (buf == NULL)
		return -EINVAL;

	if ((*ppos > max_size) ||
	    (*ppos + count > max_size) ||
	    (*ppos + count < count) ||
	    (count > uncopied_bytes))
		return -EINVAL;

	if (copy_from_user(buf + (*ppos), user_buf, count)) {
		kfree(buf);
		buf = NULL;
		return -EFAULT;
	}

	uncopied_bytes -= count;
	*ppos += count;

	if (!uncopied_bytes) {
		status = acpi_install_method(buf);
		kfree(buf);
		buf = NULL;
		if (ACPI_FAILURE(status))
			return -EINVAL;
		add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
	}

	return count;
}

static const struct file_operations cm_fops = {
	.write = cm_write,
	.llseek = default_llseek,
};

static int __init acpi_custom_method_init(void)
{
	if (acpi_debugfs_dir == NULL)
		return -ENOENT;

	cm_dentry = debugfs_create_file("custom_method", S_IWUSR,
					acpi_debugfs_dir, NULL, &cm_fops);
	if (cm_dentry == NULL)
		return -ENODEV;

	return 0;
}

static void __exit acpi_custom_method_exit(void)
{
	if (cm_dentry)
		debugfs_remove(cm_dentry);
 }

module_init(acpi_custom_method_init);
module_exit(acpi_custom_method_exit);
EXPORT_SYMBOL_GPL(acpi_debugfs_dir);