xref: /openbmc/linux/fs/efivarfs/super.c (revision 55e43d6abd078ed6d219902ce8cb4d68e3c993ba)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2d68772b7SMatt Fleming /*
3d68772b7SMatt Fleming  * Copyright (C) 2012 Red Hat, Inc.
4d68772b7SMatt Fleming  * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com>
5d68772b7SMatt Fleming  */
6d68772b7SMatt Fleming 
7d68772b7SMatt Fleming #include <linux/ctype.h>
8d68772b7SMatt Fleming #include <linux/efi.h>
9d68772b7SMatt Fleming #include <linux/fs.h>
1047999745SDavid Howells #include <linux/fs_context.h>
11d68772b7SMatt Fleming #include <linux/module.h>
12d68772b7SMatt Fleming #include <linux/pagemap.h>
13a614e192SMatt Fleming #include <linux/ucs2_string.h>
1420b4fb48SLinus Torvalds #include <linux/slab.h>
1520b4fb48SLinus Torvalds #include <linux/magic.h>
16d86ff333SAnisse Astier #include <linux/statfs.h>
170049fe7eSIlias Apalodimas #include <linux/printk.h>
18d68772b7SMatt Fleming 
19d68772b7SMatt Fleming #include "internal.h"
20d68772b7SMatt Fleming 
21d68772b7SMatt Fleming LIST_HEAD(efivarfs_list);
22d68772b7SMatt Fleming 
efivarfs_evict_inode(struct inode * inode)23d68772b7SMatt Fleming static void efivarfs_evict_inode(struct inode *inode)
24d68772b7SMatt Fleming {
25d68772b7SMatt Fleming 	clear_inode(inode);
26d68772b7SMatt Fleming }
27d68772b7SMatt Fleming 
efivarfs_statfs(struct dentry * dentry,struct kstatfs * buf)28d86ff333SAnisse Astier static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf)
29d86ff333SAnisse Astier {
30d86ff333SAnisse Astier 	const u32 attr = EFI_VARIABLE_NON_VOLATILE |
31d86ff333SAnisse Astier 			 EFI_VARIABLE_BOOTSERVICE_ACCESS |
32d86ff333SAnisse Astier 			 EFI_VARIABLE_RUNTIME_ACCESS;
33d86ff333SAnisse Astier 	u64 storage_space, remaining_space, max_variable_size;
34d86ff333SAnisse Astier 	efi_status_t status;
35d86ff333SAnisse Astier 
3679b83606SHeinrich Schuchardt 	/* Some UEFI firmware does not implement QueryVariableInfo() */
3779b83606SHeinrich Schuchardt 	storage_space = remaining_space = 0;
3879b83606SHeinrich Schuchardt 	if (efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) {
3979b83606SHeinrich Schuchardt 		status = efivar_query_variable_info(attr, &storage_space,
4079b83606SHeinrich Schuchardt 						    &remaining_space,
41d86ff333SAnisse Astier 						    &max_variable_size);
4279b83606SHeinrich Schuchardt 		if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED)
4379b83606SHeinrich Schuchardt 			pr_warn_ratelimited("query_variable_info() failed: 0x%lx\n",
4479b83606SHeinrich Schuchardt 					    status);
4579b83606SHeinrich Schuchardt 	}
46d86ff333SAnisse Astier 
47d86ff333SAnisse Astier 	/*
48d86ff333SAnisse Astier 	 * This is not a normal filesystem, so no point in pretending it has a block
49d86ff333SAnisse Astier 	 * size; we declare f_bsize to 1, so that we can then report the exact value
50d86ff333SAnisse Astier 	 * sent by EFI QueryVariableInfo in f_blocks and f_bfree
51d86ff333SAnisse Astier 	 */
52d86ff333SAnisse Astier 	buf->f_bsize	= 1;
53d86ff333SAnisse Astier 	buf->f_namelen	= NAME_MAX;
54d86ff333SAnisse Astier 	buf->f_blocks	= storage_space;
55d86ff333SAnisse Astier 	buf->f_bfree	= remaining_space;
56d86ff333SAnisse Astier 	buf->f_type	= dentry->d_sb->s_magic;
57d86ff333SAnisse Astier 
58d86ff333SAnisse Astier 	/*
59d86ff333SAnisse Astier 	 * In f_bavail we declare the free space that the kernel will allow writing
60d86ff333SAnisse Astier 	 * when the storage_paranoia x86 quirk is active. To use more, users
61d86ff333SAnisse Astier 	 * should boot the kernel with efi_no_storage_paranoia.
62d86ff333SAnisse Astier 	 */
63d86ff333SAnisse Astier 	if (remaining_space > efivar_reserved_space())
64d86ff333SAnisse Astier 		buf->f_bavail = remaining_space - efivar_reserved_space();
65d86ff333SAnisse Astier 	else
66d86ff333SAnisse Astier 		buf->f_bavail = 0;
67d86ff333SAnisse Astier 
68d86ff333SAnisse Astier 	return 0;
69d86ff333SAnisse Astier }
70d68772b7SMatt Fleming static const struct super_operations efivarfs_ops = {
71d86ff333SAnisse Astier 	.statfs = efivarfs_statfs,
72d68772b7SMatt Fleming 	.drop_inode = generic_delete_inode,
73d68772b7SMatt Fleming 	.evict_inode = efivarfs_evict_inode,
74d68772b7SMatt Fleming };
75d68772b7SMatt Fleming 
76d68772b7SMatt Fleming /*
77d68772b7SMatt Fleming  * Compare two efivarfs file names.
78d68772b7SMatt Fleming  *
79d68772b7SMatt Fleming  * An efivarfs filename is composed of two parts,
80d68772b7SMatt Fleming  *
81d68772b7SMatt Fleming  *	1. A case-sensitive variable name
82d68772b7SMatt Fleming  *	2. A case-insensitive GUID
83d68772b7SMatt Fleming  *
84d68772b7SMatt Fleming  * So we need to perform a case-sensitive match on part 1 and a
85d68772b7SMatt Fleming  * case-insensitive match on part 2.
86d68772b7SMatt Fleming  */
efivarfs_d_compare(const struct dentry * dentry,unsigned int len,const char * str,const struct qstr * name)876fa67e70SAl Viro static int efivarfs_d_compare(const struct dentry *dentry,
88d68772b7SMatt Fleming 			      unsigned int len, const char *str,
89d68772b7SMatt Fleming 			      const struct qstr *name)
90d68772b7SMatt Fleming {
91d68772b7SMatt Fleming 	int guid = len - EFI_VARIABLE_GUID_LEN;
92d68772b7SMatt Fleming 
93d68772b7SMatt Fleming 	if (name->len != len)
94d68772b7SMatt Fleming 		return 1;
95d68772b7SMatt Fleming 
96d68772b7SMatt Fleming 	/* Case-sensitive compare for the variable name */
97d68772b7SMatt Fleming 	if (memcmp(str, name->name, guid))
98d68772b7SMatt Fleming 		return 1;
99d68772b7SMatt Fleming 
100d68772b7SMatt Fleming 	/* Case-insensitive compare for the GUID */
101d68772b7SMatt Fleming 	return strncasecmp(name->name + guid, str + guid, EFI_VARIABLE_GUID_LEN);
102d68772b7SMatt Fleming }
103d68772b7SMatt Fleming 
efivarfs_d_hash(const struct dentry * dentry,struct qstr * qstr)104da53be12SLinus Torvalds static int efivarfs_d_hash(const struct dentry *dentry, struct qstr *qstr)
105d68772b7SMatt Fleming {
1068387ff25SLinus Torvalds 	unsigned long hash = init_name_hash(dentry);
107d68772b7SMatt Fleming 	const unsigned char *s = qstr->name;
108d68772b7SMatt Fleming 	unsigned int len = qstr->len;
109d68772b7SMatt Fleming 
110d68772b7SMatt Fleming 	while (len-- > EFI_VARIABLE_GUID_LEN)
111d68772b7SMatt Fleming 		hash = partial_name_hash(*s++, hash);
112d68772b7SMatt Fleming 
113d68772b7SMatt Fleming 	/* GUID is case-insensitive. */
114d68772b7SMatt Fleming 	while (len--)
115d68772b7SMatt Fleming 		hash = partial_name_hash(tolower(*s++), hash);
116d68772b7SMatt Fleming 
117d68772b7SMatt Fleming 	qstr->hash = end_name_hash(hash);
118d68772b7SMatt Fleming 	return 0;
119d68772b7SMatt Fleming }
120d68772b7SMatt Fleming 
121e37dcbfbSFabian Frederick static const struct dentry_operations efivarfs_d_ops = {
122d68772b7SMatt Fleming 	.d_compare = efivarfs_d_compare,
123d68772b7SMatt Fleming 	.d_hash = efivarfs_d_hash,
124b26d4cd3SAl Viro 	.d_delete = always_delete_dentry,
125d68772b7SMatt Fleming };
126d68772b7SMatt Fleming 
efivarfs_alloc_dentry(struct dentry * parent,char * name)127d68772b7SMatt Fleming static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name)
128d68772b7SMatt Fleming {
129d68772b7SMatt Fleming 	struct dentry *d;
130d68772b7SMatt Fleming 	struct qstr q;
131d68772b7SMatt Fleming 	int err;
132d68772b7SMatt Fleming 
133d68772b7SMatt Fleming 	q.name = name;
134d68772b7SMatt Fleming 	q.len = strlen(name);
135d68772b7SMatt Fleming 
1368387ff25SLinus Torvalds 	err = efivarfs_d_hash(parent, &q);
137d68772b7SMatt Fleming 	if (err)
138d68772b7SMatt Fleming 		return ERR_PTR(err);
139d68772b7SMatt Fleming 
140d68772b7SMatt Fleming 	d = d_alloc(parent, &q);
141d68772b7SMatt Fleming 	if (d)
142d68772b7SMatt Fleming 		return d;
143d68772b7SMatt Fleming 
144d68772b7SMatt Fleming 	return ERR_PTR(-ENOMEM);
145d68772b7SMatt Fleming }
146d68772b7SMatt Fleming 
efivarfs_callback(efi_char16_t * name16,efi_guid_t vendor,unsigned long name_size,void * data)147d68772b7SMatt Fleming static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
148d68772b7SMatt Fleming 			     unsigned long name_size, void *data)
149d68772b7SMatt Fleming {
150d68772b7SMatt Fleming 	struct super_block *sb = (struct super_block *)data;
151d68772b7SMatt Fleming 	struct efivar_entry *entry;
152d68772b7SMatt Fleming 	struct inode *inode = NULL;
153d68772b7SMatt Fleming 	struct dentry *dentry, *root = sb->s_root;
154d68772b7SMatt Fleming 	unsigned long size = 0;
155d68772b7SMatt Fleming 	char *name;
156e0d64e6aSPeter Jones 	int len;
157d68772b7SMatt Fleming 	int err = -ENOMEM;
158ed8b0de5SPeter Jones 	bool is_removable = false;
159d68772b7SMatt Fleming 
16063ffb573SJason A. Donenfeld 	if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID))
16163ffb573SJason A. Donenfeld 		return 0;
16263ffb573SJason A. Donenfeld 
163c57dcb56SRoss Lagerwall 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
164d68772b7SMatt Fleming 	if (!entry)
165d68772b7SMatt Fleming 		return err;
166d68772b7SMatt Fleming 
167d68772b7SMatt Fleming 	memcpy(entry->var.VariableName, name16, name_size);
168d68772b7SMatt Fleming 	memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
169d68772b7SMatt Fleming 
170e0d64e6aSPeter Jones 	len = ucs2_utf8size(entry->var.VariableName);
171d68772b7SMatt Fleming 
172d68772b7SMatt Fleming 	/* name, plus '-', plus GUID, plus NUL*/
173d68772b7SMatt Fleming 	name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
174d68772b7SMatt Fleming 	if (!name)
175d68772b7SMatt Fleming 		goto fail;
176d68772b7SMatt Fleming 
177e0d64e6aSPeter Jones 	ucs2_as_utf8(name, entry->var.VariableName, len);
178d68772b7SMatt Fleming 
179ed8b0de5SPeter Jones 	if (efivar_variable_is_removable(entry->var.VendorGuid, name, len))
180ed8b0de5SPeter Jones 		is_removable = true;
181ed8b0de5SPeter Jones 
182d68772b7SMatt Fleming 	name[len] = '-';
183d68772b7SMatt Fleming 
18426e02272SBorislav Petkov 	efi_guid_to_str(&entry->var.VendorGuid, name + len + 1);
185d68772b7SMatt Fleming 
186d68772b7SMatt Fleming 	name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';
187d68772b7SMatt Fleming 
188336af6a4SMichael Schaller 	/* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */
189336af6a4SMichael Schaller 	strreplace(name, '/', '!');
190336af6a4SMichael Schaller 
191ed8b0de5SPeter Jones 	inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
192ed8b0de5SPeter Jones 				   is_removable);
193d68772b7SMatt Fleming 	if (!inode)
194d68772b7SMatt Fleming 		goto fail_name;
195d68772b7SMatt Fleming 
196d68772b7SMatt Fleming 	dentry = efivarfs_alloc_dentry(root, name);
197d68772b7SMatt Fleming 	if (IS_ERR(dentry)) {
198d68772b7SMatt Fleming 		err = PTR_ERR(dentry);
199d68772b7SMatt Fleming 		goto fail_inode;
200d68772b7SMatt Fleming 	}
201d68772b7SMatt Fleming 
202ec3507b2SArd Biesheuvel 	__efivar_entry_get(entry, NULL, &size, NULL);
203ec3507b2SArd Biesheuvel 	__efivar_entry_add(entry, &efivarfs_list);
204d68772b7SMatt Fleming 
20522c2b77fSMatt Fleming 	/* copied by the above to local storage in the dentry. */
20622c2b77fSMatt Fleming 	kfree(name);
20722c2b77fSMatt Fleming 
2085955102cSAl Viro 	inode_lock(inode);
209d68772b7SMatt Fleming 	inode->i_private = entry;
210d68772b7SMatt Fleming 	i_size_write(inode, size + sizeof(entry->var.Attributes));
2115955102cSAl Viro 	inode_unlock(inode);
212d68772b7SMatt Fleming 	d_add(dentry, inode);
213d68772b7SMatt Fleming 
214d68772b7SMatt Fleming 	return 0;
215d68772b7SMatt Fleming 
216d68772b7SMatt Fleming fail_inode:
217d68772b7SMatt Fleming 	iput(inode);
218d68772b7SMatt Fleming fail_name:
219d68772b7SMatt Fleming 	kfree(name);
220d68772b7SMatt Fleming fail:
221d68772b7SMatt Fleming 	kfree(entry);
222d68772b7SMatt Fleming 	return err;
223d68772b7SMatt Fleming }
224d68772b7SMatt Fleming 
efivarfs_destroy(struct efivar_entry * entry,void * data)225d68772b7SMatt Fleming static int efivarfs_destroy(struct efivar_entry *entry, void *data)
226d68772b7SMatt Fleming {
2273a75f9f2SArd Biesheuvel 	efivar_entry_remove(entry);
228d68772b7SMatt Fleming 	kfree(entry);
229d68772b7SMatt Fleming 	return 0;
230d68772b7SMatt Fleming }
231d68772b7SMatt Fleming 
efivarfs_fill_super(struct super_block * sb,struct fs_context * fc)23247999745SDavid Howells static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc)
233d68772b7SMatt Fleming {
234d68772b7SMatt Fleming 	struct inode *inode = NULL;
235d68772b7SMatt Fleming 	struct dentry *root;
236d68772b7SMatt Fleming 	int err;
237d68772b7SMatt Fleming 
238301de9a2SJohan Hovold 	if (!efivar_is_available())
239301de9a2SJohan Hovold 		return -EOPNOTSUPP;
240301de9a2SJohan Hovold 
241d68772b7SMatt Fleming 	sb->s_maxbytes          = MAX_LFS_FILESIZE;
24209cbfeafSKirill A. Shutemov 	sb->s_blocksize         = PAGE_SIZE;
24309cbfeafSKirill A. Shutemov 	sb->s_blocksize_bits    = PAGE_SHIFT;
244d68772b7SMatt Fleming 	sb->s_magic             = EFIVARFS_MAGIC;
245d68772b7SMatt Fleming 	sb->s_op                = &efivarfs_ops;
246d68772b7SMatt Fleming 	sb->s_d_op		= &efivarfs_d_ops;
247d68772b7SMatt Fleming 	sb->s_time_gran         = 1;
248d68772b7SMatt Fleming 
249f88814ccSArd Biesheuvel 	if (!efivar_supports_writes())
250f88814ccSArd Biesheuvel 		sb->s_flags |= SB_RDONLY;
251f88814ccSArd Biesheuvel 
252ed8b0de5SPeter Jones 	inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true);
253d68772b7SMatt Fleming 	if (!inode)
254d68772b7SMatt Fleming 		return -ENOMEM;
255d68772b7SMatt Fleming 	inode->i_op = &efivarfs_dir_inode_operations;
256d68772b7SMatt Fleming 
257d68772b7SMatt Fleming 	root = d_make_root(inode);
258d68772b7SMatt Fleming 	sb->s_root = root;
259d68772b7SMatt Fleming 	if (!root)
260d68772b7SMatt Fleming 		return -ENOMEM;
261d68772b7SMatt Fleming 
262d68772b7SMatt Fleming 	INIT_LIST_HEAD(&efivarfs_list);
263d68772b7SMatt Fleming 
2641cfd6316SJulia Lawall 	err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list);
265d68772b7SMatt Fleming 	if (err)
2663a75f9f2SArd Biesheuvel 		efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
267d68772b7SMatt Fleming 
268d68772b7SMatt Fleming 	return err;
269d68772b7SMatt Fleming }
270d68772b7SMatt Fleming 
efivarfs_get_tree(struct fs_context * fc)27147999745SDavid Howells static int efivarfs_get_tree(struct fs_context *fc)
272d68772b7SMatt Fleming {
27347999745SDavid Howells 	return get_tree_single(fc, efivarfs_fill_super);
27447999745SDavid Howells }
27547999745SDavid Howells 
efivarfs_reconfigure(struct fs_context * fc)2760049fe7eSIlias Apalodimas static int efivarfs_reconfigure(struct fs_context *fc)
2770049fe7eSIlias Apalodimas {
2780049fe7eSIlias Apalodimas 	if (!efivar_supports_writes() && !(fc->sb_flags & SB_RDONLY)) {
2790049fe7eSIlias Apalodimas 		pr_err("Firmware does not support SetVariableRT. Can not remount with rw\n");
2800049fe7eSIlias Apalodimas 		return -EINVAL;
2810049fe7eSIlias Apalodimas 	}
2820049fe7eSIlias Apalodimas 
2830049fe7eSIlias Apalodimas 	return 0;
2840049fe7eSIlias Apalodimas }
2850049fe7eSIlias Apalodimas 
28647999745SDavid Howells static const struct fs_context_operations efivarfs_context_ops = {
28747999745SDavid Howells 	.get_tree	= efivarfs_get_tree,
2880049fe7eSIlias Apalodimas 	.reconfigure	= efivarfs_reconfigure,
28947999745SDavid Howells };
29047999745SDavid Howells 
efivarfs_init_fs_context(struct fs_context * fc)29147999745SDavid Howells static int efivarfs_init_fs_context(struct fs_context *fc)
29247999745SDavid Howells {
29347999745SDavid Howells 	fc->ops = &efivarfs_context_ops;
29447999745SDavid Howells 	return 0;
295d68772b7SMatt Fleming }
296d68772b7SMatt Fleming 
efivarfs_kill_sb(struct super_block * sb)297d68772b7SMatt Fleming static void efivarfs_kill_sb(struct super_block *sb)
298d68772b7SMatt Fleming {
299*48be1364SArd Biesheuvel 	struct efivarfs_fs_info *sfi = sb->s_fs_info;
300*48be1364SArd Biesheuvel 
301d68772b7SMatt Fleming 	kill_litter_super(sb);
302d68772b7SMatt Fleming 
303301de9a2SJohan Hovold 	if (!efivar_is_available())
304301de9a2SJohan Hovold 		return;
305301de9a2SJohan Hovold 
306d68772b7SMatt Fleming 	/* Remove all entries and destroy */
3073a75f9f2SArd Biesheuvel 	efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
308*48be1364SArd Biesheuvel 	kfree(sfi);
309d68772b7SMatt Fleming }
310d68772b7SMatt Fleming 
311d68772b7SMatt Fleming static struct file_system_type efivarfs_type = {
312af5a29aeSMathias Krause 	.owner   = THIS_MODULE,
313d68772b7SMatt Fleming 	.name    = "efivarfs",
31447999745SDavid Howells 	.init_fs_context = efivarfs_init_fs_context,
315d68772b7SMatt Fleming 	.kill_sb = efivarfs_kill_sb,
316d68772b7SMatt Fleming };
317d68772b7SMatt Fleming 
efivarfs_init(void)318d68772b7SMatt Fleming static __init int efivarfs_init(void)
319d68772b7SMatt Fleming {
320d68772b7SMatt Fleming 	return register_filesystem(&efivarfs_type);
321d68772b7SMatt Fleming }
322d68772b7SMatt Fleming 
efivarfs_exit(void)323af5a29aeSMathias Krause static __exit void efivarfs_exit(void)
324af5a29aeSMathias Krause {
325af5a29aeSMathias Krause 	unregister_filesystem(&efivarfs_type);
326af5a29aeSMathias Krause }
327af5a29aeSMathias Krause 
328d68772b7SMatt Fleming MODULE_AUTHOR("Matthew Garrett, Jeremy Kerr");
329d68772b7SMatt Fleming MODULE_DESCRIPTION("EFI Variable Filesystem");
330d68772b7SMatt Fleming MODULE_LICENSE("GPL");
331d68772b7SMatt Fleming MODULE_ALIAS_FS("efivarfs");
332d68772b7SMatt Fleming 
333d68772b7SMatt Fleming module_init(efivarfs_init);
334af5a29aeSMathias Krause module_exit(efivarfs_exit);
335