Commit e7b4b1f6 authored by Ard Biesheuvel's avatar Ard Biesheuvel
Browse files

Merge branch 'efivarfs' into next

parents 8a32d46b 908af31f
Loading
Loading
Loading
Loading
+50 −9
Original line number Diff line number Diff line
@@ -36,28 +36,41 @@ static ssize_t efivarfs_file_write(struct file *file,
	if (IS_ERR(data))
		return PTR_ERR(data);

	inode_lock(inode);
	if (var->removed) {
		/*
		 * file got removed; don't allow a set.  Caused by an
		 * unsuccessful create or successful delete write
		 * racing with us.
		 */
		bytes = -EIO;
		goto out;
	}

	bytes = efivar_entry_set_get_size(var, attributes, &datasize,
					  data, &set);
	if (!set && bytes) {
	if (!set) {
		if (bytes == -ENOENT)
			bytes = -EIO;
		goto out;
	}

	if (bytes == -ENOENT) {
		drop_nlink(inode);
		d_delete(file->f_path.dentry);
		dput(file->f_path.dentry);
		/*
		 * zero size signals to release that the write deleted
		 * the variable
		 */
		i_size_write(inode, 0);
	} else {
		inode_lock(inode);
		i_size_write(inode, datasize + sizeof(attributes));
		inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
		inode_unlock(inode);
	}

	bytes = count;

out:
	inode_unlock(inode);

	kfree(data);

	return bytes;
@@ -106,8 +119,36 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
	return size;
}

static int efivarfs_file_release(struct inode *inode, struct file *file)
{
	struct efivar_entry *var = inode->i_private;

	inode_lock(inode);
	var->removed = (--var->open_count == 0 && i_size_read(inode) == 0);
	inode_unlock(inode);

	if (var->removed)
		simple_recursive_removal(file->f_path.dentry, NULL);

	return 0;
}

static int efivarfs_file_open(struct inode *inode, struct file *file)
{
	struct efivar_entry *entry = inode->i_private;

	file->private_data = entry;

	inode_lock(inode);
	entry->open_count++;
	inode_unlock(inode);

	return 0;
}

const struct file_operations efivarfs_file_operations = {
	.open	= simple_open,
	.open		= efivarfs_file_open,
	.read		= efivarfs_file_read,
	.write		= efivarfs_file_write,
	.release	= efivarfs_file_release,
};
+13 −28
Original line number Diff line number Diff line
@@ -77,39 +77,34 @@ static bool efivarfs_valid_name(const char *str, int len)
static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir,
			   struct dentry *dentry, umode_t mode, bool excl)
{
	struct efivarfs_fs_info *info = dir->i_sb->s_fs_info;
	struct inode *inode = NULL;
	struct efivar_entry *var;
	int namelen, i = 0, err = 0;
	bool is_removable = false;
	efi_guid_t vendor;

	if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len))
		return -EINVAL;

	var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
	if (!var)
		return -ENOMEM;

	/* length of the variable name itself: remove GUID and separator */
	namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1;

	err = guid_parse(dentry->d_name.name + namelen + 1, &var->var.VendorGuid);
	err = guid_parse(dentry->d_name.name + namelen + 1, &vendor);
	if (err)
		goto out;
	if (guid_equal(&var->var.VendorGuid, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) {
		err = -EPERM;
		goto out;
	}
		return err;
	if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID))
		return -EPERM;

	if (efivar_variable_is_removable(var->var.VendorGuid,
	if (efivar_variable_is_removable(vendor,
					 dentry->d_name.name, namelen))
		is_removable = true;

	inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0, is_removable);
	if (!inode) {
		err = -ENOMEM;
		goto out;
	}
	if (!inode)
		return -ENOMEM;
	var = efivar_entry(inode);

	var->var.VendorGuid = vendor;

	for (i = 0; i < namelen; i++)
		var->var.VariableName[i] = dentry->d_name.name[i];
@@ -117,21 +112,11 @@ static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir,
	var->var.VariableName[i] = '\0';

	inode->i_private = var;
	kmemleak_ignore(var);

	err = efivar_entry_add(var, &info->efivarfs_list);
	if (err)
		goto out;

	d_instantiate(dentry, inode);
	dget(dentry);
out:
	if (err) {
		kfree(var);
		if (inode)
			iput(inode);
	}
	return err;

	return 0;
}

static int efivarfs_unlink(struct inode *dir, struct dentry *dentry)
+13 −13
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@
#ifndef EFIVAR_FS_INTERNAL_H
#define EFIVAR_FS_INTERNAL_H

#include <linux/list.h>
#include <linux/efi.h>

struct efivarfs_mount_opts {
@@ -16,7 +15,6 @@ struct efivarfs_mount_opts {

struct efivarfs_fs_info {
	struct efivarfs_mount_opts mount_opts;
	struct list_head efivarfs_list;
	struct super_block *sb;
	struct notifier_block nb;
};
@@ -24,22 +22,23 @@ struct efivarfs_fs_info {
struct efi_variable {
	efi_char16_t  VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
	efi_guid_t    VendorGuid;
	__u32         Attributes;
};

struct efivar_entry {
	struct efi_variable var;
	struct list_head list;
	struct kobject kobj;
	struct inode vfs_inode;
	unsigned long open_count;
	bool removed;
};

int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *,
			    struct list_head *),
		void *data, struct list_head *head);
static inline struct efivar_entry *efivar_entry(struct inode *inode)
{
	return container_of(inode, struct efivar_entry, vfs_inode);
}

int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
		void *data);

int efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
void efivar_entry_remove(struct efivar_entry *entry);
int efivar_entry_delete(struct efivar_entry *entry);

int efivar_entry_size(struct efivar_entry *entry, unsigned long *size);
@@ -50,13 +49,14 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
			      unsigned long *size, void *data, bool *set);

int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
		      struct list_head *head, void *data);

bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
		     unsigned long data_size);
bool efivar_variable_is_removable(efi_guid_t vendor, const char *name,
				  size_t len);
char *efivar_get_utf8name(const efi_char16_t *name16, efi_guid_t *vendor);
bool efivarfs_variable_is_present(efi_char16_t *variable_name,
				  efi_guid_t *vendor, void *data);

extern const struct file_operations efivarfs_file_operations;
extern const struct inode_operations efivarfs_dir_inode_operations;
+62 −44
Original line number Diff line number Diff line
@@ -39,9 +39,24 @@ static int efivarfs_ops_notifier(struct notifier_block *nb, unsigned long event,
	return NOTIFY_OK;
}

static void efivarfs_evict_inode(struct inode *inode)
static struct inode *efivarfs_alloc_inode(struct super_block *sb)
{
	clear_inode(inode);
	struct efivar_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL);

	if (!entry)
		return NULL;

	inode_init_once(&entry->vfs_inode);
	entry->removed = false;

	return &entry->vfs_inode;
}

static void efivarfs_free_inode(struct inode *inode)
{
	struct efivar_entry *entry = efivar_entry(inode);

	kfree(entry);
}

static int efivarfs_show_options(struct seq_file *m, struct dentry *root)
@@ -106,7 +121,8 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf)
static const struct super_operations efivarfs_ops = {
	.statfs = efivarfs_statfs,
	.drop_inode = generic_delete_inode,
	.evict_inode = efivarfs_evict_inode,
	.alloc_inode = efivarfs_alloc_inode,
	.free_inode = efivarfs_free_inode,
	.show_options = efivarfs_show_options,
};

@@ -181,9 +197,37 @@ static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name)
	return ERR_PTR(-ENOMEM);
}

bool efivarfs_variable_is_present(efi_char16_t *variable_name,
				  efi_guid_t *vendor, void *data)
{
	char *name = efivar_get_utf8name(variable_name, vendor);
	struct super_block *sb = data;
	struct dentry *dentry;
	struct qstr qstr;

	if (!name)
		/*
		 * If the allocation failed there'll already be an
		 * error in the log (and likely a huge and growing
		 * number of them since they system will be under
		 * extreme memory pressure), so simply assume
		 * collision for safety but don't add to the log
		 * flood.
		 */
		return true;

	qstr.name = name;
	qstr.len = strlen(name);
	dentry = d_hash_and_lookup(sb->s_root, &qstr);
	kfree(name);
	if (!IS_ERR_OR_NULL(dentry))
		dput(dentry);

	return dentry != NULL;
}

static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
			     unsigned long name_size, void *data,
			     struct list_head *list)
			     unsigned long name_size, void *data)
{
	struct super_block *sb = (struct super_block *)data;
	struct efivar_entry *entry;
@@ -198,39 +242,26 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
	if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID))
		return 0;

	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
	if (!entry)
		return err;

	memcpy(entry->var.VariableName, name16, name_size);
	memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));

	len = ucs2_utf8size(entry->var.VariableName);

	/* name, plus '-', plus GUID, plus NUL*/
	name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
	name = efivar_get_utf8name(name16, &vendor);
	if (!name)
		goto fail;
		return err;

	ucs2_as_utf8(name, entry->var.VariableName, len);
	/* length of the variable name itself: remove GUID and separator */
	len = strlen(name) - EFI_VARIABLE_GUID_LEN - 1;

	if (efivar_variable_is_removable(entry->var.VendorGuid, name, len))
	if (efivar_variable_is_removable(vendor, name, len))
		is_removable = true;

	name[len] = '-';

	efi_guid_to_str(&entry->var.VendorGuid, name + len + 1);

	name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';

	/* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */
	strreplace(name, '/', '!');

	inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
				   is_removable);
	if (!inode)
		goto fail_name;

	entry = efivar_entry(inode);

	memcpy(entry->var.VariableName, name16, name_size);
	memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));

	dentry = efivarfs_alloc_dentry(root, name);
	if (IS_ERR(dentry)) {
		err = PTR_ERR(dentry);
@@ -238,14 +269,13 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
	}

	__efivar_entry_get(entry, NULL, &size, NULL);
	__efivar_entry_add(entry, list);

	/* copied by the above to local storage in the dentry. */
	kfree(name);

	inode_lock(inode);
	inode->i_private = entry;
	i_size_write(inode, size + sizeof(entry->var.Attributes));
	i_size_write(inode, size + sizeof(__u32)); /* attributes + data */
	inode_unlock(inode);
	d_add(dentry, inode);

@@ -255,16 +285,8 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
	iput(inode);
fail_name:
	kfree(name);
fail:
	kfree(entry);
	return err;
}

static int efivarfs_destroy(struct efivar_entry *entry, void *data)
{
	efivar_entry_remove(entry);
	kfree(entry);
	return 0;
	return err;
}

enum {
@@ -336,7 +358,7 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc)
	if (err)
		return err;

	return efivar_init(efivarfs_callback, sb, &sfi->efivarfs_list);
	return efivar_init(efivarfs_callback, sb);
}

static int efivarfs_get_tree(struct fs_context *fc)
@@ -371,8 +393,6 @@ static int efivarfs_init_fs_context(struct fs_context *fc)
	if (!sfi)
		return -ENOMEM;

	INIT_LIST_HEAD(&sfi->efivarfs_list);

	sfi->mount_opts.uid = GLOBAL_ROOT_UID;
	sfi->mount_opts.gid = GLOBAL_ROOT_GID;

@@ -388,8 +408,6 @@ static void efivarfs_kill_sb(struct super_block *sb)
	blocking_notifier_chain_unregister(&efivar_ops_nh, &sfi->nb);
	kill_litter_super(sb);

	/* Remove all entries and destroy */
	efivar_entry_iter(efivarfs_destroy, &sfi->efivarfs_list, NULL);
	kfree(sfi);
}

+41 −138
Original line number Diff line number Diff line
@@ -225,6 +225,31 @@ variable_matches(const char *var_name, size_t len, const char *match_name,
	}
}

char *
efivar_get_utf8name(const efi_char16_t *name16, efi_guid_t *vendor)
{
	int len = ucs2_utf8size(name16);
	char *name;

	/* name, plus '-', plus GUID, plus NUL*/
	name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
	if (!name)
		return NULL;

	ucs2_as_utf8(name, name16, len);

	name[len] = '-';

	efi_guid_to_str(vendor, name + len + 1);

	name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';

	/* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */
	strreplace(name, '/', '!');

	return name;
}

bool
efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
		unsigned long data_size)
@@ -288,28 +313,6 @@ efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
	return found;
}

static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
				struct list_head *head)
{
	struct efivar_entry *entry, *n;
	unsigned long strsize1, strsize2;
	bool found = false;

	strsize1 = ucs2_strsize(variable_name, EFI_VAR_NAME_LEN);
	list_for_each_entry_safe(entry, n, head, list) {
		strsize2 = ucs2_strsize(entry->var.VariableName, EFI_VAR_NAME_LEN);
		if (strsize1 == strsize2 &&
			!memcmp(variable_name, &(entry->var.VariableName),
				strsize2) &&
			!efi_guidcmp(entry->var.VendorGuid,
				*vendor)) {
			found = true;
			break;
		}
	}
	return found;
}

/*
 * Returns the size of variable_name, in bytes, including the
 * terminating NULL character, or variable_name_size if no NULL
@@ -361,16 +364,14 @@ static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid,
 * efivar_init - build the initial list of EFI variables
 * @func: callback function to invoke for every variable
 * @data: function-specific data to pass to @func
 * @head: initialised head of variable list
 *
 * Get every EFI variable from the firmware and invoke @func. @func
 * should call efivar_entry_add() to build the list of variables.
 * should populate the initial dentry and inode tree.
 *
 * Returns 0 on success, or a kernel error code on failure.
 */
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *,
			    struct list_head *),
		void *data, struct list_head *head)
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
		void *data)
{
	unsigned long variable_name_size = 512;
	efi_char16_t *variable_name;
@@ -414,14 +415,14 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *,
			 * we'll ever see a different variable name,
			 * and may end up looping here forever.
			 */
			if (variable_is_present(variable_name, &vendor_guid,
						head)) {
			if (efivarfs_variable_is_present(variable_name,
							 &vendor_guid, data)) {
				dup_variable_bug(variable_name, &vendor_guid,
						 variable_name_size);
				status = EFI_NOT_FOUND;
			} else {
				err = func(variable_name, vendor_guid,
					   variable_name_size, data, head);
					   variable_name_size, data);
				if (err)
					status = EFI_NOT_FOUND;
			}
@@ -453,70 +454,12 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *,
}

/**
 * efivar_entry_add - add entry to variable list
 * @entry: entry to add to list
 * @head: list head
 *
 * Returns 0 on success, or a kernel error code on failure.
 */
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
{
	int err;

	err = efivar_lock();
	if (err)
		return err;
	list_add(&entry->list, head);
	efivar_unlock();

	return 0;
}

/**
 * __efivar_entry_add - add entry to variable list
 * @entry: entry to add to list
 * @head: list head
 */
void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
{
	list_add(&entry->list, head);
}

/**
 * efivar_entry_remove - remove entry from variable list
 * @entry: entry to remove from list
 *
 * Returns 0 on success, or a kernel error code on failure.
 */
void efivar_entry_remove(struct efivar_entry *entry)
{
	list_del(&entry->list);
}

/*
 * efivar_entry_list_del_unlock - remove entry from variable list
 * @entry: entry to remove
 *
 * Remove @entry from the variable list and release the list lock.
 *
 * NOTE: slightly weird locking semantics here - we expect to be
 * called with the efivars lock already held, and we release it before
 * returning. This is because this function is usually called after
 * set_variable() while the lock is still held.
 */
static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
{
	list_del(&entry->list);
	efivar_unlock();
}

/**
 * efivar_entry_delete - delete variable and remove entry from list
 * efivar_entry_delete - delete variable
 * @entry: entry containing variable to delete
 *
 * Delete the variable from the firmware and remove @entry from the
 * variable list. It is the caller's responsibility to free @entry
 * once we return.
 * Delete the variable from the firmware. It is the caller's
 * responsibility to free @entry (by deleting the dentry/inode) once
 * we return.
 *
 * Returns 0 on success, -EINTR if we can't grab the semaphore,
 * converted EFI status code if set_variable() fails.
@@ -533,12 +476,10 @@ int efivar_entry_delete(struct efivar_entry *entry)
	status = efivar_set_variable_locked(entry->var.VariableName,
					    &entry->var.VendorGuid,
					    0, 0, NULL, false);
	if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) {
	efivar_unlock();
	if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND))
		return efi_status_to_err(status);
	}

	efivar_entry_list_del_unlock(entry);
	return 0;
}

@@ -632,7 +573,7 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
 * get_variable() fail.
 *
 * If the EFI variable does not exist when calling set_variable()
 * (EFI_NOT_FOUND), @entry is removed from the variable list.
 * (EFI_NOT_FOUND).
 */
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
			      unsigned long *size, void *data, bool *set)
@@ -648,9 +589,8 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
		return -EINVAL;

	/*
	 * The lock here protects the get_variable call, the conditional
	 * set_variable call, and removal of the variable from the efivars
	 * list (in the case of an authenticated delete).
	 * The lock here protects the get_variable call and the
	 * conditional set_variable call
	 */
	err = efivar_lock();
	if (err)
@@ -676,9 +616,6 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
				    &entry->var.VendorGuid,
				    NULL, size, NULL);

	if (status == EFI_NOT_FOUND)
		efivar_entry_list_del_unlock(entry);
	else
	efivar_unlock();

	if (status && status != EFI_BUFFER_TOO_SMALL)
@@ -691,37 +628,3 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
	return err;

}

/**
 * efivar_entry_iter - iterate over variable list
 * @func: callback function
 * @head: head of variable list
 * @data: function-specific data to pass to callback
 *
 * Iterate over the list of EFI variables and call @func with every
 * entry on the list. It is safe for @func to remove entries in the
 * list via efivar_entry_delete() while iterating.
 *
 * Some notes for the callback function:
 *  - a non-zero return value indicates an error and terminates the loop
 *  - @func is called from atomic context
 */
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
		      struct list_head *head, void *data)
{
	struct efivar_entry *entry, *n;
	int err = 0;

	err = efivar_lock();
	if (err)
		return err;

	list_for_each_entry_safe(entry, n, head, list) {
		err = func(entry, data);
		if (err)
			break;
	}
	efivar_unlock();

	return err;
}