cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

efi-pstore.c (10928B)


      1// SPDX-License-Identifier: GPL-2.0+
      2
      3#include <linux/efi.h>
      4#include <linux/module.h>
      5#include <linux/pstore.h>
      6#include <linux/slab.h>
      7#include <linux/ucs2_string.h>
      8
      9#define DUMP_NAME_LEN 66
     10
     11#define EFIVARS_DATA_SIZE_MAX 1024
     12
     13static bool efivars_pstore_disable =
     14	IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
     15
     16module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
     17
     18#define PSTORE_EFI_ATTRIBUTES \
     19	(EFI_VARIABLE_NON_VOLATILE | \
     20	 EFI_VARIABLE_BOOTSERVICE_ACCESS | \
     21	 EFI_VARIABLE_RUNTIME_ACCESS)
     22
     23static LIST_HEAD(efi_pstore_list);
     24static DECLARE_WORK(efivar_work, NULL);
     25
     26static int efi_pstore_open(struct pstore_info *psi)
     27{
     28	psi->data = NULL;
     29	return 0;
     30}
     31
     32static int efi_pstore_close(struct pstore_info *psi)
     33{
     34	psi->data = NULL;
     35	return 0;
     36}
     37
     38static inline u64 generic_id(u64 timestamp, unsigned int part, int count)
     39{
     40	return (timestamp * 100 + part) * 1000 + count;
     41}
     42
     43static int efi_pstore_read_func(struct efivar_entry *entry,
     44				struct pstore_record *record)
     45{
     46	efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
     47	char name[DUMP_NAME_LEN], data_type;
     48	int i;
     49	int cnt;
     50	unsigned int part;
     51	unsigned long size;
     52	u64 time;
     53
     54	if (efi_guidcmp(entry->var.VendorGuid, vendor))
     55		return 0;
     56
     57	for (i = 0; i < DUMP_NAME_LEN; i++)
     58		name[i] = entry->var.VariableName[i];
     59
     60	if (sscanf(name, "dump-type%u-%u-%d-%llu-%c",
     61		   &record->type, &part, &cnt, &time, &data_type) == 5) {
     62		record->id = generic_id(time, part, cnt);
     63		record->part = part;
     64		record->count = cnt;
     65		record->time.tv_sec = time;
     66		record->time.tv_nsec = 0;
     67		if (data_type == 'C')
     68			record->compressed = true;
     69		else
     70			record->compressed = false;
     71		record->ecc_notice_size = 0;
     72	} else if (sscanf(name, "dump-type%u-%u-%d-%llu",
     73		   &record->type, &part, &cnt, &time) == 4) {
     74		record->id = generic_id(time, part, cnt);
     75		record->part = part;
     76		record->count = cnt;
     77		record->time.tv_sec = time;
     78		record->time.tv_nsec = 0;
     79		record->compressed = false;
     80		record->ecc_notice_size = 0;
     81	} else if (sscanf(name, "dump-type%u-%u-%llu",
     82			  &record->type, &part, &time) == 3) {
     83		/*
     84		 * Check if an old format,
     85		 * which doesn't support holding
     86		 * multiple logs, remains.
     87		 */
     88		record->id = generic_id(time, part, 0);
     89		record->part = part;
     90		record->count = 0;
     91		record->time.tv_sec = time;
     92		record->time.tv_nsec = 0;
     93		record->compressed = false;
     94		record->ecc_notice_size = 0;
     95	} else
     96		return 0;
     97
     98	entry->var.DataSize = 1024;
     99	__efivar_entry_get(entry, &entry->var.Attributes,
    100			   &entry->var.DataSize, entry->var.Data);
    101	size = entry->var.DataSize;
    102	memcpy(record->buf, entry->var.Data,
    103	       (size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size));
    104
    105	return size;
    106}
    107
    108/**
    109 * efi_pstore_scan_sysfs_enter
    110 * @pos: scanning entry
    111 * @next: next entry
    112 * @head: list head
    113 */
    114static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
    115					struct efivar_entry *next,
    116					struct list_head *head)
    117{
    118	pos->scanning = true;
    119	if (&next->list != head)
    120		next->scanning = true;
    121}
    122
    123/**
    124 * __efi_pstore_scan_sysfs_exit
    125 * @entry: deleting entry
    126 * @turn_off_scanning: Check if a scanning flag should be turned off
    127 */
    128static inline int __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
    129						bool turn_off_scanning)
    130{
    131	if (entry->deleting) {
    132		list_del(&entry->list);
    133		efivar_entry_iter_end();
    134		kfree(entry);
    135		if (efivar_entry_iter_begin())
    136			return -EINTR;
    137	} else if (turn_off_scanning)
    138		entry->scanning = false;
    139
    140	return 0;
    141}
    142
    143/**
    144 * efi_pstore_scan_sysfs_exit
    145 * @pos: scanning entry
    146 * @next: next entry
    147 * @head: list head
    148 * @stop: a flag checking if scanning will stop
    149 */
    150static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
    151				       struct efivar_entry *next,
    152				       struct list_head *head, bool stop)
    153{
    154	int ret = __efi_pstore_scan_sysfs_exit(pos, true);
    155
    156	if (ret)
    157		return ret;
    158
    159	if (stop)
    160		ret = __efi_pstore_scan_sysfs_exit(next, &next->list != head);
    161	return ret;
    162}
    163
    164/**
    165 * efi_pstore_sysfs_entry_iter
    166 *
    167 * @record: pstore record to pass to callback
    168 *
    169 * You MUST call efivar_entry_iter_begin() before this function, and
    170 * efivar_entry_iter_end() afterwards.
    171 *
    172 */
    173static int efi_pstore_sysfs_entry_iter(struct pstore_record *record)
    174{
    175	struct efivar_entry **pos = (struct efivar_entry **)&record->psi->data;
    176	struct efivar_entry *entry, *n;
    177	struct list_head *head = &efi_pstore_list;
    178	int size = 0;
    179	int ret;
    180
    181	if (!*pos) {
    182		list_for_each_entry_safe(entry, n, head, list) {
    183			efi_pstore_scan_sysfs_enter(entry, n, head);
    184
    185			size = efi_pstore_read_func(entry, record);
    186			ret = efi_pstore_scan_sysfs_exit(entry, n, head,
    187							 size < 0);
    188			if (ret)
    189				return ret;
    190			if (size)
    191				break;
    192		}
    193		*pos = n;
    194		return size;
    195	}
    196
    197	list_for_each_entry_safe_from((*pos), n, head, list) {
    198		efi_pstore_scan_sysfs_enter((*pos), n, head);
    199
    200		size = efi_pstore_read_func((*pos), record);
    201		ret = efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
    202		if (ret)
    203			return ret;
    204		if (size)
    205			break;
    206	}
    207	*pos = n;
    208	return size;
    209}
    210
    211/**
    212 * efi_pstore_read
    213 *
    214 * This function returns a size of NVRAM entry logged via efi_pstore_write().
    215 * The meaning and behavior of efi_pstore/pstore are as below.
    216 *
    217 * size > 0: Got data of an entry logged via efi_pstore_write() successfully,
    218 *           and pstore filesystem will continue reading subsequent entries.
    219 * size == 0: Entry was not logged via efi_pstore_write(),
    220 *            and efi_pstore driver will continue reading subsequent entries.
    221 * size < 0: Failed to get data of entry logging via efi_pstore_write(),
    222 *           and pstore will stop reading entry.
    223 */
    224static ssize_t efi_pstore_read(struct pstore_record *record)
    225{
    226	ssize_t size;
    227
    228	record->buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
    229	if (!record->buf)
    230		return -ENOMEM;
    231
    232	if (efivar_entry_iter_begin()) {
    233		size = -EINTR;
    234		goto out;
    235	}
    236	size = efi_pstore_sysfs_entry_iter(record);
    237	efivar_entry_iter_end();
    238
    239out:
    240	if (size <= 0) {
    241		kfree(record->buf);
    242		record->buf = NULL;
    243	}
    244	return size;
    245}
    246
    247static int efi_pstore_write(struct pstore_record *record)
    248{
    249	char name[DUMP_NAME_LEN];
    250	efi_char16_t efi_name[DUMP_NAME_LEN];
    251	efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
    252	int i, ret = 0;
    253
    254	record->id = generic_id(record->time.tv_sec, record->part,
    255				record->count);
    256
    257	/* Since we copy the entire length of name, make sure it is wiped. */
    258	memset(name, 0, sizeof(name));
    259
    260	snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c",
    261		 record->type, record->part, record->count,
    262		 (long long)record->time.tv_sec,
    263		 record->compressed ? 'C' : 'D');
    264
    265	for (i = 0; i < DUMP_NAME_LEN; i++)
    266		efi_name[i] = name[i];
    267
    268	ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
    269			      false, record->size, record->psi->buf);
    270
    271	if (record->reason == KMSG_DUMP_OOPS && try_module_get(THIS_MODULE))
    272		if (!schedule_work(&efivar_work))
    273			module_put(THIS_MODULE);
    274
    275	return ret;
    276};
    277
    278/*
    279 * Clean up an entry with the same name
    280 */
    281static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
    282{
    283	efi_char16_t *efi_name = data;
    284	efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
    285	unsigned long ucs2_len = ucs2_strlen(efi_name);
    286
    287	if (efi_guidcmp(entry->var.VendorGuid, vendor))
    288		return 0;
    289
    290	if (ucs2_strncmp(entry->var.VariableName, efi_name, (size_t)ucs2_len))
    291		return 0;
    292
    293	if (entry->scanning) {
    294		/*
    295		 * Skip deletion because this entry will be deleted
    296		 * after scanning is completed.
    297		 */
    298		entry->deleting = true;
    299	} else
    300		list_del(&entry->list);
    301
    302	/* found */
    303	__efivar_entry_delete(entry);
    304
    305	return 1;
    306}
    307
    308static int efi_pstore_erase_name(const char *name)
    309{
    310	struct efivar_entry *entry = NULL;
    311	efi_char16_t efi_name[DUMP_NAME_LEN];
    312	int found, i;
    313
    314	for (i = 0; i < DUMP_NAME_LEN; i++) {
    315		efi_name[i] = name[i];
    316		if (name[i] == '\0')
    317			break;
    318	}
    319
    320	if (efivar_entry_iter_begin())
    321		return -EINTR;
    322
    323	found = __efivar_entry_iter(efi_pstore_erase_func, &efi_pstore_list,
    324				    efi_name, &entry);
    325	efivar_entry_iter_end();
    326
    327	if (found && !entry->scanning)
    328		kfree(entry);
    329
    330	return found ? 0 : -ENOENT;
    331}
    332
    333static int efi_pstore_erase(struct pstore_record *record)
    334{
    335	char name[DUMP_NAME_LEN];
    336	int ret;
    337
    338	snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld",
    339		 record->type, record->part, record->count,
    340		 (long long)record->time.tv_sec);
    341	ret = efi_pstore_erase_name(name);
    342	if (ret != -ENOENT)
    343		return ret;
    344
    345	snprintf(name, sizeof(name), "dump-type%u-%u-%lld",
    346		record->type, record->part, (long long)record->time.tv_sec);
    347	ret = efi_pstore_erase_name(name);
    348
    349	return ret;
    350}
    351
    352static struct pstore_info efi_pstore_info = {
    353	.owner		= THIS_MODULE,
    354	.name		= "efi",
    355	.flags		= PSTORE_FLAGS_DMESG,
    356	.open		= efi_pstore_open,
    357	.close		= efi_pstore_close,
    358	.read		= efi_pstore_read,
    359	.write		= efi_pstore_write,
    360	.erase		= efi_pstore_erase,
    361};
    362
    363static int efi_pstore_callback(efi_char16_t *name, efi_guid_t vendor,
    364			       unsigned long name_size, void *data)
    365{
    366	struct efivar_entry *entry;
    367	int ret;
    368
    369	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
    370	if (!entry)
    371		return -ENOMEM;
    372
    373	memcpy(entry->var.VariableName, name, name_size);
    374	entry->var.VendorGuid = vendor;
    375
    376	ret = efivar_entry_add(entry, &efi_pstore_list);
    377	if (ret)
    378		kfree(entry);
    379
    380	return ret;
    381}
    382
    383static int efi_pstore_update_entry(efi_char16_t *name, efi_guid_t vendor,
    384				   unsigned long name_size, void *data)
    385{
    386	struct efivar_entry *entry = data;
    387
    388	if (efivar_entry_find(name, vendor, &efi_pstore_list, false))
    389		return 0;
    390
    391	memcpy(entry->var.VariableName, name, name_size);
    392	memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
    393
    394	return 1;
    395}
    396
    397static void efi_pstore_update_entries(struct work_struct *work)
    398{
    399	struct efivar_entry *entry;
    400	int err;
    401
    402	/* Add new sysfs entries */
    403	while (1) {
    404		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
    405		if (!entry)
    406			return;
    407
    408		err = efivar_init(efi_pstore_update_entry, entry,
    409				  false, &efi_pstore_list);
    410		if (!err)
    411			break;
    412
    413		efivar_entry_add(entry, &efi_pstore_list);
    414	}
    415
    416	kfree(entry);
    417	module_put(THIS_MODULE);
    418}
    419
    420static __init int efivars_pstore_init(void)
    421{
    422	int ret;
    423
    424	if (!efivars_kobject() || !efivar_supports_writes())
    425		return 0;
    426
    427	if (efivars_pstore_disable)
    428		return 0;
    429
    430	ret = efivar_init(efi_pstore_callback, NULL, true, &efi_pstore_list);
    431	if (ret)
    432		return ret;
    433
    434	efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
    435	if (!efi_pstore_info.buf)
    436		return -ENOMEM;
    437
    438	efi_pstore_info.bufsize = 1024;
    439
    440	if (pstore_register(&efi_pstore_info)) {
    441		kfree(efi_pstore_info.buf);
    442		efi_pstore_info.buf = NULL;
    443		efi_pstore_info.bufsize = 0;
    444	}
    445
    446	INIT_WORK(&efivar_work, efi_pstore_update_entries);
    447
    448	return 0;
    449}
    450
    451static __exit void efivars_pstore_exit(void)
    452{
    453	if (!efi_pstore_info.bufsize)
    454		return;
    455
    456	pstore_unregister(&efi_pstore_info);
    457	kfree(efi_pstore_info.buf);
    458	efi_pstore_info.buf = NULL;
    459	efi_pstore_info.bufsize = 0;
    460}
    461
    462module_init(efivars_pstore_init);
    463module_exit(efivars_pstore_exit);
    464
    465MODULE_DESCRIPTION("EFI variable backend for pstore");
    466MODULE_LICENSE("GPL");
    467MODULE_ALIAS("platform:efivars");