Commit message: ACPI: configfs: Disallow loading ACPI tables when locked down

Like other vectors already patched, this one here allows the root
user to load ACPI tables, which enables arbitrary physical address
writes, which in turn makes it possible to disable lockdown.

Prevents this by checking the lockdown status before allowing a new
ACPI table to be installed. The link in the trailer shows a PoC of
how this might be used.

Link: https://git.zx2c4.com/american-unsigned-language/tree/american-unsigned-language-2.sh
Cc: 5.4+ <stable@vger.kernel.org> # 5.4+
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
CVE: CVE-2020-15780
CWE: CWE-862
Vulnerability description: An issue was discovered in drivers/acpi/acpi_configfs.c in the Linux kernel before 5.7.7. Injection of malicious ACPI tables via configfs could be used by attackers to bypass lockdown and secure boot restrictions, aka CID-75b0cea7bf30.

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_aml_read(struct config_item *cfg,
				   void *data, size_t size)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	if (data)
		memcpy(data, h, h->length);

	return h->length;
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_oem_revision_show(struct config_item *cfg, char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%d\n", h->oem_revision);
}

Project: linux
File: acpi_configfs.c

static void __exit acpi_configfs_exit(void)
{
	configfs_unregister_default_group(acpi_table_group);
	configfs_unregister_subsystem(&acpi_configfs);
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_aml_write(struct config_item *cfg,
				    const void *data, size_t size)
{
	const struct acpi_table_header *header = data;
	struct acpi_table *table;
	int ret = security_locked_down(LOCKDOWN_ACPI_TABLES);

	if (ret)
		return ret;

	table = container_of(cfg, struct acpi_table, cfg);

	if (table->header) {
		pr_err("table already loaded\n");
		return -EBUSY;
	}

	if (header->length != size) {
		pr_err("invalid table length\n");
		return -EINVAL;
	}

	if (memcmp(header->signature, ACPI_SIG_SSDT, 4)) {
		pr_err("invalid table signature\n");
		return -EINVAL;
	}

	table = container_of(cfg, struct acpi_table, cfg);

	table->header = kmemdup(header, header->length, GFP_KERNEL);
	if (!table->header)
		return -ENOMEM;

	ret = acpi_load_table(table->header, &table->index);
	if (ret) {
		kfree(table->header);
		table->header = NULL;
	}

	return ret;
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_revision_show(struct config_item *cfg, char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%d\n", h->revision);
}

Project: linux
File: acpi_configfs.c

static inline struct acpi_table_header *get_header(struct config_item *cfg)
{
	struct acpi_table *table = container_of(cfg, struct acpi_table, cfg);

	if (!table->header)
		pr_err("table not loaded\n");

	return table->header;
}

Project: linux
File: acpi_configfs.c

static int __init acpi_configfs_init(void)
{
	int ret;
	struct config_group *root = &acpi_configfs.su_group;

	config_group_init(root);

	ret = configfs_register_subsystem(&acpi_configfs);
	if (ret)
		return ret;

	acpi_table_group = configfs_register_default_group(root, "table",
							   &acpi_tables_type);
	return PTR_ERR_OR_ZERO(acpi_table_group);
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_signature_show(struct config_item *cfg, char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%.*s\n", ACPI_NAMESEG_SIZE, h->signature);
}

Project: linux
File: acpi_configfs.c

static void acpi_table_drop_item(struct config_group *group,
				 struct config_item *cfg)
{
	struct acpi_table *table = container_of(cfg, struct acpi_table, cfg);

	ACPI_INFO(("Host-directed Dynamic ACPI Table Unload"));
	acpi_unload_table(table->index);
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_asl_compiler_id_show(struct config_item *cfg,
					       char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%.*s\n", ACPI_NAMESEG_SIZE, h->asl_compiler_id);
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_oem_table_id_show(struct config_item *cfg, char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%.*s\n", ACPI_OEM_TABLE_ID_SIZE, h->oem_table_id);
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_oem_id_show(struct config_item *cfg, char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%.*s\n", ACPI_OEM_ID_SIZE, h->oem_id);
}

Project: linux
File: acpi_configfs.c

static struct config_item *acpi_table_make_item(struct config_group *group,
						const char *name)
{
	struct acpi_table *table;

	table = kzalloc(sizeof(*table), GFP_KERNEL);
	if (!table)
		return ERR_PTR(-ENOMEM);

	config_item_init_type_name(&table->cfg, name, &acpi_table_type);
	return &table->cfg;
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_length_show(struct config_item *cfg, char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%d\n", h->length);
}

Project: linux
File: acpi_configfs.c

static ssize_t acpi_table_asl_compiler_revision_show(struct config_item *cfg,
						     char *str)
{
	struct acpi_table_header *h = get_header(cfg);

	if (!h)
		return -EINVAL;

	return sprintf(str, "%d\n", h->asl_compiler_revision);
}