162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2012 Red Hat, Inc. 462306a36Sopenharmony_ci * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/efi.h> 862306a36Sopenharmony_ci#include <linux/fs.h> 962306a36Sopenharmony_ci#include <linux/ctype.h> 1062306a36Sopenharmony_ci#include <linux/kmemleak.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/uuid.h> 1362306a36Sopenharmony_ci#include <linux/fileattr.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "internal.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic const struct inode_operations efivarfs_file_inode_operations; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct inode *efivarfs_get_inode(struct super_block *sb, 2062306a36Sopenharmony_ci const struct inode *dir, int mode, 2162306a36Sopenharmony_ci dev_t dev, bool is_removable) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci struct inode *inode = new_inode(sb); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci if (inode) { 2662306a36Sopenharmony_ci inode->i_ino = get_next_ino(); 2762306a36Sopenharmony_ci inode->i_mode = mode; 2862306a36Sopenharmony_ci inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode); 2962306a36Sopenharmony_ci inode->i_flags = is_removable ? 0 : S_IMMUTABLE; 3062306a36Sopenharmony_ci switch (mode & S_IFMT) { 3162306a36Sopenharmony_ci case S_IFREG: 3262306a36Sopenharmony_ci inode->i_op = &efivarfs_file_inode_operations; 3362306a36Sopenharmony_ci inode->i_fop = &efivarfs_file_operations; 3462306a36Sopenharmony_ci break; 3562306a36Sopenharmony_ci case S_IFDIR: 3662306a36Sopenharmony_ci inode->i_op = &efivarfs_dir_inode_operations; 3762306a36Sopenharmony_ci inode->i_fop = &simple_dir_operations; 3862306a36Sopenharmony_ci inc_nlink(inode); 3962306a36Sopenharmony_ci break; 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci } 4262306a36Sopenharmony_ci return inode; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* 4662306a36Sopenharmony_ci * Return true if 'str' is a valid efivarfs filename of the form, 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci * VariableName-12345678-1234-1234-1234-1234567891bc 4962306a36Sopenharmony_ci */ 5062306a36Sopenharmony_cibool efivarfs_valid_name(const char *str, int len) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci const char *s = str + len - EFI_VARIABLE_GUID_LEN; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* 5562306a36Sopenharmony_ci * We need a GUID, plus at least one letter for the variable name, 5662306a36Sopenharmony_ci * plus the '-' separator 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_ci if (len < EFI_VARIABLE_GUID_LEN + 2) 5962306a36Sopenharmony_ci return false; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci /* GUID must be preceded by a '-' */ 6262306a36Sopenharmony_ci if (*(s - 1) != '-') 6362306a36Sopenharmony_ci return false; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* 6662306a36Sopenharmony_ci * Validate that 's' is of the correct format, e.g. 6762306a36Sopenharmony_ci * 6862306a36Sopenharmony_ci * 12345678-1234-1234-1234-123456789abc 6962306a36Sopenharmony_ci */ 7062306a36Sopenharmony_ci return uuid_is_valid(s); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, 7462306a36Sopenharmony_ci struct dentry *dentry, umode_t mode, bool excl) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct inode *inode = NULL; 7762306a36Sopenharmony_ci struct efivar_entry *var; 7862306a36Sopenharmony_ci int namelen, i = 0, err = 0; 7962306a36Sopenharmony_ci bool is_removable = false; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) 8262306a36Sopenharmony_ci return -EINVAL; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); 8562306a36Sopenharmony_ci if (!var) 8662306a36Sopenharmony_ci return -ENOMEM; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* length of the variable name itself: remove GUID and separator */ 8962306a36Sopenharmony_ci namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci err = guid_parse(dentry->d_name.name + namelen + 1, &var->var.VendorGuid); 9262306a36Sopenharmony_ci if (err) 9362306a36Sopenharmony_ci goto out; 9462306a36Sopenharmony_ci if (guid_equal(&var->var.VendorGuid, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) { 9562306a36Sopenharmony_ci err = -EPERM; 9662306a36Sopenharmony_ci goto out; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci if (efivar_variable_is_removable(var->var.VendorGuid, 10062306a36Sopenharmony_ci dentry->d_name.name, namelen)) 10162306a36Sopenharmony_ci is_removable = true; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0, is_removable); 10462306a36Sopenharmony_ci if (!inode) { 10562306a36Sopenharmony_ci err = -ENOMEM; 10662306a36Sopenharmony_ci goto out; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci for (i = 0; i < namelen; i++) 11062306a36Sopenharmony_ci var->var.VariableName[i] = dentry->d_name.name[i]; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci var->var.VariableName[i] = '\0'; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci inode->i_private = var; 11562306a36Sopenharmony_ci kmemleak_ignore(var); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci err = efivar_entry_add(var, &efivarfs_list); 11862306a36Sopenharmony_ci if (err) 11962306a36Sopenharmony_ci goto out; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci d_instantiate(dentry, inode); 12262306a36Sopenharmony_ci dget(dentry); 12362306a36Sopenharmony_ciout: 12462306a36Sopenharmony_ci if (err) { 12562306a36Sopenharmony_ci kfree(var); 12662306a36Sopenharmony_ci if (inode) 12762306a36Sopenharmony_ci iput(inode); 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci return err; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int efivarfs_unlink(struct inode *dir, struct dentry *dentry) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct efivar_entry *var = d_inode(dentry)->i_private; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (efivar_entry_delete(var)) 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci drop_nlink(d_inode(dentry)); 14062306a36Sopenharmony_ci dput(dentry); 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ciconst struct inode_operations efivarfs_dir_inode_operations = { 14562306a36Sopenharmony_ci .lookup = simple_lookup, 14662306a36Sopenharmony_ci .unlink = efivarfs_unlink, 14762306a36Sopenharmony_ci .create = efivarfs_create, 14862306a36Sopenharmony_ci}; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic int 15162306a36Sopenharmony_ciefivarfs_fileattr_get(struct dentry *dentry, struct fileattr *fa) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci unsigned int i_flags; 15462306a36Sopenharmony_ci unsigned int flags = 0; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci i_flags = d_inode(dentry)->i_flags; 15762306a36Sopenharmony_ci if (i_flags & S_IMMUTABLE) 15862306a36Sopenharmony_ci flags |= FS_IMMUTABLE_FL; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci fileattr_fill_flags(fa, flags); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci return 0; 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic int 16662306a36Sopenharmony_ciefivarfs_fileattr_set(struct mnt_idmap *idmap, 16762306a36Sopenharmony_ci struct dentry *dentry, struct fileattr *fa) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci unsigned int i_flags = 0; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (fileattr_has_fsx(fa)) 17262306a36Sopenharmony_ci return -EOPNOTSUPP; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (fa->flags & ~FS_IMMUTABLE_FL) 17562306a36Sopenharmony_ci return -EOPNOTSUPP; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (fa->flags & FS_IMMUTABLE_FL) 17862306a36Sopenharmony_ci i_flags |= S_IMMUTABLE; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci inode_set_flags(d_inode(dentry), i_flags, S_IMMUTABLE); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic const struct inode_operations efivarfs_file_inode_operations = { 18662306a36Sopenharmony_ci .fileattr_get = efivarfs_fileattr_get, 18762306a36Sopenharmony_ci .fileattr_set = efivarfs_fileattr_set, 18862306a36Sopenharmony_ci}; 189