Commit 0389075e authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'x86-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 fixes from Ingo Molnar:
 "This is unusually large, partly due to the EFI fixes that prevent
  accidental deletion of EFI variables through efivarfs that may brick
  machines.  These fixes are somewhat involved to maintain compatibility
  with existing install methods and other usage modes, while trying to
  turn off the 'rm -rf' bricking vector.

  Other fixes are for large page ioremap()s and for non-temporal
  user-memcpy()s"

* 'x86-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/mm: Fix vmalloc_fault() to handle large pages properly
  hpet: Drop stale URLs
  x86/uaccess/64: Handle the caching of 4-byte nocache copies properly in __copy_user_nocache()
  x86/uaccess/64: Make the __copy_user_nocache() assembly code more readable
  lib/ucs2_string: Correct ucs2 -> utf8 conversion
  efi: Add pstore variables to the deletion whitelist
  efi: Make efivarfs entries immutable by default
  efi: Make our variable validation list include the guid
  efi: Do variable name validation tests in utf8
  efi: Use ucs2_as_utf8 in efivarfs instead of open coding a bad version
  lib/ucs2_string: Add ucs2 -> utf8 helper functions
parents 06b74c65 f4eafd8b
...@@ -14,3 +14,10 @@ filesystem. ...@@ -14,3 +14,10 @@ filesystem.
efivarfs is typically mounted like this, efivarfs is typically mounted like this,
mount -t efivarfs none /sys/firmware/efi/efivars mount -t efivarfs none /sys/firmware/efi/efivars
Due to the presence of numerous firmware bugs where removing non-standard
UEFI variables causes the system firmware to fail to POST, efivarfs
files that are not well-known standardized variables are created
as immutable files. This doesn't prevent removal - "chattr -i" will work -
but it does prevent this kind of failure from being accomplished
accidentally.
High Precision Event Timer Driver for Linux High Precision Event Timer Driver for Linux
The High Precision Event Timer (HPET) hardware follows a specification The High Precision Event Timer (HPET) hardware follows a specification
by Intel and Microsoft which can be found at by Intel and Microsoft, revision 1.
http://www.intel.com/hardwaredesign/hpetspec_1.pdf
Each HPET has one fixed-rate counter (at 10+ MHz, hence "High Precision") Each HPET has one fixed-rate counter (at 10+ MHz, hence "High Precision")
and up to 32 comparators. Normally three or more comparators are provided, and up to 32 comparators. Normally three or more comparators are provided,
......
...@@ -778,8 +778,8 @@ config HPET_TIMER ...@@ -778,8 +778,8 @@ config HPET_TIMER
HPET is the next generation timer replacing legacy 8254s. HPET is the next generation timer replacing legacy 8254s.
The HPET provides a stable time base on SMP The HPET provides a stable time base on SMP
systems, unlike the TSC, but it is more expensive to access, systems, unlike the TSC, but it is more expensive to access,
as it is off-chip. You can find the HPET spec at as it is off-chip. The interface used is documented
<http://www.intel.com/hardwaredesign/hpetspec_1.pdf>. in the HPET spec, revision 1.
You can safely choose Y here. However, HPET will only be You can safely choose Y here. However, HPET will only be
activated if the platform and the BIOS support this feature. activated if the platform and the BIOS support this feature.
......
...@@ -232,17 +232,31 @@ ENDPROC(copy_user_enhanced_fast_string) ...@@ -232,17 +232,31 @@ ENDPROC(copy_user_enhanced_fast_string)
/* /*
* copy_user_nocache - Uncached memory copy with exception handling * copy_user_nocache - Uncached memory copy with exception handling
* This will force destination/source out of cache for more performance. * This will force destination out of cache for more performance.
*
* Note: Cached memory copy is used when destination or size is not
* naturally aligned. That is:
* - Require 8-byte alignment when size is 8 bytes or larger.
* - Require 4-byte alignment when size is 4 bytes.
*/ */
ENTRY(__copy_user_nocache) ENTRY(__copy_user_nocache)
ASM_STAC ASM_STAC
/* If size is less than 8 bytes, go to 4-byte copy */
cmpl $8,%edx cmpl $8,%edx
jb 20f /* less then 8 bytes, go to byte copy loop */ jb .L_4b_nocache_copy_entry
/* If destination is not 8-byte aligned, "cache" copy to align it */
ALIGN_DESTINATION ALIGN_DESTINATION
/* Set 4x8-byte copy count and remainder */
movl %edx,%ecx movl %edx,%ecx
andl $63,%edx andl $63,%edx
shrl $6,%ecx shrl $6,%ecx
jz 17f jz .L_8b_nocache_copy_entry /* jump if count is 0 */
/* Perform 4x8-byte nocache loop-copy */
.L_4x8b_nocache_copy_loop:
1: movq (%rsi),%r8 1: movq (%rsi),%r8
2: movq 1*8(%rsi),%r9 2: movq 1*8(%rsi),%r9
3: movq 2*8(%rsi),%r10 3: movq 2*8(%rsi),%r10
...@@ -262,60 +276,106 @@ ENTRY(__copy_user_nocache) ...@@ -262,60 +276,106 @@ ENTRY(__copy_user_nocache)
leaq 64(%rsi),%rsi leaq 64(%rsi),%rsi
leaq 64(%rdi),%rdi leaq 64(%rdi),%rdi
decl %ecx decl %ecx
jnz 1b jnz .L_4x8b_nocache_copy_loop
17: movl %edx,%ecx
/* Set 8-byte copy count and remainder */
.L_8b_nocache_copy_entry:
movl %edx,%ecx
andl $7,%edx andl $7,%edx
shrl $3,%ecx shrl $3,%ecx
jz 20f jz .L_4b_nocache_copy_entry /* jump if count is 0 */
18: movq (%rsi),%r8
19: movnti %r8,(%rdi) /* Perform 8-byte nocache loop-copy */
.L_8b_nocache_copy_loop:
20: movq (%rsi),%r8
21: movnti %r8,(%rdi)
leaq 8(%rsi),%rsi leaq 8(%rsi),%rsi
leaq 8(%rdi),%rdi leaq 8(%rdi),%rdi
decl %ecx decl %ecx
jnz 18b jnz .L_8b_nocache_copy_loop
20: andl %edx,%edx
jz 23f /* If no byte left, we're done */
.L_4b_nocache_copy_entry:
andl %edx,%edx
jz .L_finish_copy
/* If destination is not 4-byte aligned, go to byte copy: */
movl %edi,%ecx
andl $3,%ecx
jnz .L_1b_cache_copy_entry
/* Set 4-byte copy count (1 or 0) and remainder */
movl %edx,%ecx movl %edx,%ecx
21: movb (%rsi),%al andl $3,%edx
22: movb %al,(%rdi) shrl $2,%ecx
jz .L_1b_cache_copy_entry /* jump if count is 0 */
/* Perform 4-byte nocache copy: */
30: movl (%rsi),%r8d
31: movnti %r8d,(%rdi)
leaq 4(%rsi),%rsi
leaq 4(%rdi),%rdi
/* If no bytes left, we're done: */
andl %edx,%edx
jz .L_finish_copy
/* Perform byte "cache" loop-copy for the remainder */
.L_1b_cache_copy_entry:
movl %edx,%ecx
.L_1b_cache_copy_loop:
40: movb (%rsi),%al
41: movb %al,(%rdi)
incq %rsi incq %rsi
incq %rdi incq %rdi
decl %ecx decl %ecx
jnz 21b jnz .L_1b_cache_copy_loop
23: xorl %eax,%eax
/* Finished copying; fence the prior stores */
.L_finish_copy:
xorl %eax,%eax
ASM_CLAC ASM_CLAC
sfence sfence
ret ret
.section .fixup,"ax" .section .fixup,"ax"
30: shll $6,%ecx .L_fixup_4x8b_copy:
shll $6,%ecx
addl %ecx,%edx addl %ecx,%edx
jmp 60f jmp .L_fixup_handle_tail
40: lea (%rdx,%rcx,8),%rdx .L_fixup_8b_copy:
jmp 60f lea (%rdx,%rcx,8),%rdx
50: movl %ecx,%edx jmp .L_fixup_handle_tail
60: sfence .L_fixup_4b_copy:
lea (%rdx,%rcx,4),%rdx
jmp .L_fixup_handle_tail
.L_fixup_1b_copy:
movl %ecx,%edx
.L_fixup_handle_tail:
sfence
jmp copy_user_handle_tail jmp copy_user_handle_tail
.previous .previous
_ASM_EXTABLE(1b,30b) _ASM_EXTABLE(1b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(2b,30b) _ASM_EXTABLE(2b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(3b,30b) _ASM_EXTABLE(3b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(4b,30b) _ASM_EXTABLE(4b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(5b,30b) _ASM_EXTABLE(5b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(6b,30b) _ASM_EXTABLE(6b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(7b,30b) _ASM_EXTABLE(7b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(8b,30b) _ASM_EXTABLE(8b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(9b,30b) _ASM_EXTABLE(9b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(10b,30b) _ASM_EXTABLE(10b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(11b,30b) _ASM_EXTABLE(11b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(12b,30b) _ASM_EXTABLE(12b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(13b,30b) _ASM_EXTABLE(13b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(14b,30b) _ASM_EXTABLE(14b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(15b,30b) _ASM_EXTABLE(15b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(16b,30b) _ASM_EXTABLE(16b,.L_fixup_4x8b_copy)
_ASM_EXTABLE(18b,40b) _ASM_EXTABLE(20b,.L_fixup_8b_copy)
_ASM_EXTABLE(19b,40b) _ASM_EXTABLE(21b,.L_fixup_8b_copy)
_ASM_EXTABLE(21b,50b) _ASM_EXTABLE(30b,.L_fixup_4b_copy)
_ASM_EXTABLE(22b,50b) _ASM_EXTABLE(31b,.L_fixup_4b_copy)
_ASM_EXTABLE(40b,.L_fixup_1b_copy)
_ASM_EXTABLE(41b,.L_fixup_1b_copy)
ENDPROC(__copy_user_nocache) ENDPROC(__copy_user_nocache)
...@@ -287,6 +287,9 @@ static noinline int vmalloc_fault(unsigned long address) ...@@ -287,6 +287,9 @@ static noinline int vmalloc_fault(unsigned long address)
if (!pmd_k) if (!pmd_k)
return -1; return -1;
if (pmd_huge(*pmd_k))
return 0;
pte_k = pte_offset_kernel(pmd_k, address); pte_k = pte_offset_kernel(pmd_k, address);
if (!pte_present(*pte_k)) if (!pte_present(*pte_k))
return -1; return -1;
...@@ -360,8 +363,6 @@ void vmalloc_sync_all(void) ...@@ -360,8 +363,6 @@ void vmalloc_sync_all(void)
* 64-bit: * 64-bit:
* *
* Handle a fault on the vmalloc area * Handle a fault on the vmalloc area
*
* This assumes no large pages in there.
*/ */
static noinline int vmalloc_fault(unsigned long address) static noinline int vmalloc_fault(unsigned long address)
{ {
...@@ -403,17 +404,23 @@ static noinline int vmalloc_fault(unsigned long address) ...@@ -403,17 +404,23 @@ static noinline int vmalloc_fault(unsigned long address)
if (pud_none(*pud_ref)) if (pud_none(*pud_ref))
return -1; return -1;
if (pud_none(*pud) || pud_page_vaddr(*pud) != pud_page_vaddr(*pud_ref)) if (pud_none(*pud) || pud_pfn(*pud) != pud_pfn(*pud_ref))
BUG(); BUG();
if (pud_huge(*pud))
return 0;
pmd = pmd_offset(pud, address); pmd = pmd_offset(pud, address);
pmd_ref = pmd_offset(pud_ref, address); pmd_ref = pmd_offset(pud_ref, address);
if (pmd_none(*pmd_ref)) if (pmd_none(*pmd_ref))
return -1; return -1;
if (pmd_none(*pmd) || pmd_page(*pmd) != pmd_page(*pmd_ref)) if (pmd_none(*pmd) || pmd_pfn(*pmd) != pmd_pfn(*pmd_ref))
BUG(); BUG();
if (pmd_huge(*pmd))
return 0;
pte_ref = pte_offset_kernel(pmd_ref, address); pte_ref = pte_offset_kernel(pmd_ref, address);
if (!pte_present(*pte_ref)) if (!pte_present(*pte_ref))
return -1; return -1;
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
/* /*
* The High Precision Event Timer driver. * The High Precision Event Timer driver.
* This driver is closely modelled after the rtc.c driver. * This driver is closely modelled after the rtc.c driver.
* http://www.intel.com/hardwaredesign/hpetspec_1.pdf * See HPET spec revision 1.
*/ */
#define HPET_USER_FREQ (64) #define HPET_USER_FREQ (64)
#define HPET_DRIFT (500) #define HPET_DRIFT (500)
......
...@@ -221,7 +221,7 @@ sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor, ...@@ -221,7 +221,7 @@ sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor,
} }
if ((attributes & ~EFI_VARIABLE_MASK) != 0 || if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
efivar_validate(name, data, size) == false) { efivar_validate(vendor, name, data, size) == false) {
printk(KERN_ERR "efivars: Malformed variable content\n"); printk(KERN_ERR "efivars: Malformed variable content\n");
return -EINVAL; return -EINVAL;
} }
...@@ -447,7 +447,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, ...@@ -447,7 +447,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
} }
if ((attributes & ~EFI_VARIABLE_MASK) != 0 || if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
efivar_validate(name, data, size) == false) { efivar_validate(new_var->VendorGuid, name, data,
size) == false) {
printk(KERN_ERR "efivars: Malformed variable content\n"); printk(KERN_ERR "efivars: Malformed variable content\n");
return -EINVAL; return -EINVAL;
} }
...@@ -540,38 +541,30 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, ...@@ -540,38 +541,30 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
static int static int
efivar_create_sysfs_entry(struct efivar_entry *new_var) efivar_create_sysfs_entry(struct efivar_entry *new_var)
{ {
int i, short_name_size; int short_name_size;
char *short_name; char *short_name;
unsigned long variable_name_size; unsigned long utf8_name_size;
efi_char16_t *variable_name; efi_char16_t *variable_name = new_var->var.VariableName;
int ret; int ret;
variable_name = new_var->var.VariableName;
variable_name_size = ucs2_strlen(variable_name) * sizeof(efi_char16_t);
/* /*
* Length of the variable bytes in ASCII, plus the '-' separator, * Length of the variable bytes in UTF8, plus the '-' separator,
* plus the GUID, plus trailing NUL * plus the GUID, plus trailing NUL
*/ */
short_name_size = variable_name_size / sizeof(efi_char16_t) utf8_name_size = ucs2_utf8size(variable_name);
+ 1 + EFI_VARIABLE_GUID_LEN + 1; short_name_size = utf8_name_size + 1 + EFI_VARIABLE_GUID_LEN + 1;
short_name = kzalloc(short_name_size, GFP_KERNEL);
short_name = kmalloc(short_name_size, GFP_KERNEL);
if (!short_name) if (!short_name)
return -ENOMEM; return -ENOMEM;
/* Convert Unicode to normal chars (assume top bits are 0), ucs2_as_utf8(short_name, variable_name, short_name_size);
ala UTF-8 */
for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) {
short_name[i] = variable_name[i] & 0xFF;
}
/* This is ugly, but necessary to separate one vendor's /* This is ugly, but necessary to separate one vendor's
private variables from another's. */ private variables from another's. */
short_name[utf8_name_size] = '-';
*(short_name + strlen(short_name)) = '-';
efi_guid_to_str(&new_var->var.VendorGuid, efi_guid_to_str(&new_var->var.VendorGuid,
short_name + strlen(short_name)); short_name + utf8_name_size + 1);
new_var->kobj.kset = efivars_kset; new_var->kobj.kset = efivars_kset;
......
...@@ -165,66 +165,132 @@ validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer, ...@@ -165,66 +165,132 @@ validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer,
} }
struct variable_validate { struct variable_validate {
efi_guid_t vendor;
char *name; char *name;
bool (*validate)(efi_char16_t *var_name, int match, u8 *data, bool (*validate)(efi_char16_t *var_name, int match, u8 *data,
unsigned long len); unsigned long len);
}; };
/*
* This is the list of variables we need to validate, as well as the
* whitelist for what we think is safe not to default to immutable.
*
* If it has a validate() method that's not NULL, it'll go into the
* validation routine. If not, it is assumed valid, but still used for
* whitelisting.
*
* Note that it's sorted by {vendor,name}, but globbed names must come after
* any other name with the same prefix.
*/
static const struct variable_validate variable_validate[] = { static const struct variable_validate variable_validate[] = {
{ "BootNext", validate_uint16 }, { EFI_GLOBAL_VARIABLE_GUID, "BootNext", validate_uint16 },
{ "BootOrder", validate_boot_order }, { EFI_GLOBAL_VARIABLE_GUID, "BootOrder", validate_boot_order },
{ "DriverOrder", validate_boot_order }, { EFI_GLOBAL_VARIABLE_GUID, "Boot*", validate_load_option },
{ "Boot*", validate_load_option }, { EFI_GLOBAL_VARIABLE_GUID, "DriverOrder", validate_boot_order },
{ "Driver*", validate_load_option }, { EFI_GLOBAL_VARIABLE_GUID, "Driver*", validate_load_option },
{ "ConIn", validate_device_path }, { EFI_GLOBAL_VARIABLE_GUID, "ConIn", validate_device_path },
{ "ConInDev", validate_device_path }, { EFI_GLOBAL_VARIABLE_GUID, "ConInDev", validate_device_path },
{ "ConOut", validate_device_path }, { EFI_GLOBAL_VARIABLE_GUID, "ConOut", validate_device_path },
{ "ConOutDev", validate_device_path }, { EFI_GLOBAL_VARIABLE_GUID, "ConOutDev", validate_device_path },
{ "ErrOut", validate_device_path }, { EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path },
{ "ErrOutDev", validate_device_path }, { EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path },
{ "Timeout", validate_uint16 }, { EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string },
{ "Lang", validate_ascii_string }, { EFI_GLOBAL_VARIABLE_GUID, "OsIndications", NULL },
{ "PlatformLang", validate_ascii_string }, { EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string },
{ "", NULL }, { EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 },
{ LINUX_EFI_CRASH_GUID, "*", NULL },
{ NULL_GUID, "", NULL },
}; };
static bool
variable_matches(const char *var_name, size_t len, const char *match_name,
int *match)
{
for (*match = 0; ; (*match)++) {
char c = match_name[*match];
char u = var_name[*match];
/* Wildcard in the matching name means we've matched */
if (c == '*')
return true;
/* Case sensitive match */
if (!c && *match == len)
return true;
if (c != u)
return false;
if (!c)
return true;
}
return true;
}
bool bool
efivar_validate(efi_char16_t *var_name, u8 *data, unsigned long len) efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
unsigned long data_size)
{ {
int i; int i;
u16 *unicode_name = var_name; unsigned long utf8_size;
u8 *utf8_name;
utf8_size = ucs2_utf8size(var_name);
utf8_name = kmalloc(utf8_size + 1, GFP_KERNEL);
if (!utf8_name)
return false;
for (i = 0; variable_validate[i].validate != NULL; i++) { ucs2_as_utf8(utf8_name, var_name, utf8_size);
utf8_name[utf8_size] = '\0';
for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
const char *name = variable_validate[i].name; const char *name = variable_validate[i].name;
int match; int match = 0;
for (match = 0; ; match++) { if (efi_guidcmp(vendor, variable_validate[i].vendor))
char c = name[match]; continue;
u16 u = unicode_name[match];
/* All special variables are plain ascii */ if (variable_matches(utf8_name, utf8_size+1, name, &match)) {
if (u > 127) if (variable_validate[i].validate == NULL)
break;
kfree(utf8_name);
return variable_validate[i].validate(var_name, match,
data, data_size);
}
}
kfree(utf8_name);
return true; return true;
}
EXPORT_SYMBOL_GPL(efivar_validate);
/* Wildcard in the matching name means we've matched */ bool
if (c == '*') efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
return variable_validate[i].validate(var_name, size_t len)
match, data, len); {
int i;
bool found = false;
int match = 0;
/* Case sensitive match */ /*
if (c != u) * Check if our variable is in the validated variables list
break; */
for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
if (efi_guidcmp(variable_validate[i].vendor, vendor))
continue;
/* Reached the end of the string while matching */ if (variable_matches(var_name, len,
if (!c) variable_validate[i].name, &match)) {
return variable_validate[i].validate(var_name, found = true;
match, data, len); break;
} }
} }
return true; /*
* If it's in our list, it is removable.
*/
return found;
} }
EXPORT_SYMBOL_GPL(efivar_validate); EXPORT_SYMBOL_GPL(efivar_variable_is_removable);
static efi_status_t static efi_status_t
check_var_size(u32 attributes, unsigned long size) check_var_size(u32 attributes, unsigned long size)
...@@ -852,7 +918,7 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, ...@@ -852,7 +918,7 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
*set = false; *set = false;
if (efivar_validate(name, data, *size) == false) if (efivar_validate(*vendor, name, data, *size) == false)
return -EINVAL; return -EINVAL;
/* /*
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <linux/efi.h> #include <linux/efi.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/mount.h>
#include "internal.h" #include "internal.h"
...@@ -103,9 +104,78 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, ...@@ -103,9 +104,78 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
return size; return size;
} }
static int
efivarfs_ioc_getxflags(struct file *file, void __user *arg)
{
struct inode *inode = file->f_mapping->host;
unsigned int i_flags;
unsigned int flags = 0;
i_flags = inode->i_flags;
if (i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
if (copy_to_user(arg, &flags, sizeof(flags)))
return -EFAULT;
return 0;
}
static int
efivarfs_ioc_setxflags(struct file *file, void __user *arg)
{
struct inode *inode = file->f_mapping->host;
unsigned int flags;
unsigned int i_flags = 0;
int error;
if (!inode_owner_or_capable(inode))
return -EACCES;
if (copy_from_user(&flags, arg, sizeof(flags)))
return -EFAULT;
if (flags & ~FS_IMMUTABLE_FL)
return -EOPNOTSUPP;
if (!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
if (flags & FS_IMMUTABLE_FL)
i_flags |= S_IMMUTABLE;
error = mnt_want_write_file(file);
if (error)
return error;
inode_lock(inode);
inode_set_flags(inode, i_flags, S_IMMUTABLE);
inode_unlock(inode);
mnt_drop_write_file(file);
return 0;
}
long
efivarfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long p)
{
void __user *arg = (void __user *)p;
switch (cmd) {
case FS_IOC_GETFLAGS:
return efivarfs_ioc_getxflags(file, arg);
case FS_IOC_SETFLAGS:
return efivarfs_ioc_setxflags(file, arg);
}
return -ENOTTY;
}
const struct file_operations efivarfs_file_operations = { const struct file_operations efivarfs_file_operations = {
.open = simple_open, .open = simple_open,
.read = efivarfs_file_read, .read = efivarfs_file_read,
.write = efivarfs_file_write, .write = efivarfs_file_write,
.llseek = no_llseek, .llseek = no_llseek,
.unlocked_ioctl = efivarfs_file_ioctl,
}; };
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
#include "internal.h" #include "internal.h"
struct inode *efivarfs_get_inode(struct super_block *sb, struct inode *efivarfs_get_inode(struct super_block *sb,
const struct inode *dir, int mode, dev_t dev) const struct inode *dir, int mode,
dev_t dev, bool is_removable)
{ {
struct inode *inode = new_inode(sb); struct inode *inode = new_inode(sb);
...@@ -23,6 +24,7 @@ struct inode *efivarfs_get_inode(struct super_block *sb, ...@@ -23,6 +24,7 @@ struct inode *efivarfs_get_inode(struct super_block *sb,
inode->i_ino = get_next_ino(); inode->i_ino = get_next_ino();
inode->i_mode = mode; inode->i_mode = mode;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
inode->i_flags = is_removable ? 0 : S_IMMUTABLE;
switch (mode & S_IFMT) { switch (mode & S_IFMT) {
case S_IFREG: case S_IFREG:
inode->i_fop = &efivarfs_file_operations; inode->i_fop = &efivarfs_file_operations;
...@@ -102,22 +104,17 @@ static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid) ...@@ -102,22 +104,17 @@ static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid)
static int efivarfs_create(struct inode *dir, struct dentry *dentry, static int efivarfs_create(struct inode *dir, struct dentry *dentry,
umode_t mode, bool excl) umode_t mode, bool excl)
{ {
struct inode *inode; struct inode *inode = NULL;
struct efivar_entry *var; struct efivar_entry *var;
int namelen, i = 0, err = 0; int namelen, i = 0, err = 0;
bool is_removable = false;
if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len))
return -EINVAL; return -EINVAL;
inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0);
if (!inode)
return -ENOMEM;
var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
if (!var) { if (!var)
err = -ENOMEM; return -ENOMEM;
goto out;
}
/* length of the variable name itself: remove GUID and separator */ /* length of the variable name itself: remove GUID and separator */
namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1; namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1;
...@@ -125,6 +122,16 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry, ...@@ -125,6 +122,16 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1, efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1,
&var->var.VendorGuid); &var->var.VendorGuid);
if (efivar_variable_is_removable(var->var.VendorGuid,
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;
}
for (i = 0; i < namelen; i++) for (i = 0; i < namelen; i++)
var->var.VariableName[i] = dentry->d_name.name[i]; var->var.VariableName[i] = dentry->d_name.name[i];
...@@ -138,6 +145,7 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry, ...@@ -138,6 +145,7 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
out: out:
if (err) { if (err) {
kfree(var); kfree(var);
if (inode)
iput(inode); iput(inode);
} }
return err; return err;
......
...@@ -15,7 +15,8 @@ extern const struct file_operations efivarfs_file_operations; ...@@ -15,7 +15,8 @@ extern const struct file_operations efivarfs_file_operations;
extern const struct inode_operations efivarfs_dir_inode_operations; extern const struct inode_operations efivarfs_dir_inode_operations;
extern bool efivarfs_valid_name(const char *str, int len); extern bool efivarfs_valid_name(const char *str, int len);
extern struct inode *efivarfs_get_inode(struct super_block *sb, extern struct inode *efivarfs_get_inode(struct super_block *sb,
const struct inode *dir, int mode, dev_t dev); const struct inode *dir, int mode, dev_t dev,
bool is_removable);
extern struct list_head efivarfs_list; extern struct list_head efivarfs_list;
......
...@@ -118,8 +118,9 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, ...@@ -118,8 +118,9 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
struct dentry *dentry, *root = sb->s_root; struct dentry *dentry, *root = sb->s_root;
unsigned long size = 0; unsigned long size = 0;
char *name; char *name;
int len, i; int len;
int err = -ENOMEM; int err = -ENOMEM;
bool is_removable = false;
entry = kzalloc(sizeof(*entry), GFP_KERNEL); entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) if (!entry)
...@@ -128,15 +129,17 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, ...@@ -128,15 +129,17 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
memcpy(entry->var.VariableName, name16, name_size); memcpy(entry->var.VariableName, name16, name_size);
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
len = ucs2_strlen(entry->var.VariableName); len = ucs2_utf8size(entry->var.VariableName);
/* name, plus '-', plus GUID, plus NUL*/ /* name, plus '-', plus GUID, plus NUL*/
name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
if (!name) if (!name)
goto fail; goto fail;
for (i = 0; i < len; i++) ucs2_as_utf8(name, entry->var.VariableName, len);
name[i] = entry->var.VariableName[i] & 0xFF;
if (efivar_variable_is_removable(entry->var.VendorGuid, name, len))
is_removable = true;
name[len] = '-'; name[len] = '-';
...@@ -144,7 +147,8 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, ...@@ -144,7 +147,8 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';
inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0); inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
is_removable);
if (!inode) if (!inode)
goto fail_name; goto fail_name;
...@@ -200,7 +204,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) ...@@ -200,7 +204,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
sb->s_d_op = &efivarfs_d_ops; sb->s_d_op = &efivarfs_d_ops;
sb->s_time_gran = 1; sb->s_time_gran = 1;
inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0); inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true);
if (!inode) if (!inode)
return -ENOMEM; return -ENOMEM;
inode->i_op = &efivarfs_dir_inode_operations; inode->i_op = &efivarfs_dir_inode_operations;
......
...@@ -1199,7 +1199,10 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *), ...@@ -1199,7 +1199,10 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid, struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
struct list_head *head, bool remove); struct list_head *head, bool remove);
bool efivar_validate(efi_char16_t *var_name, u8 *data, unsigned long len); 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);
extern struct work_struct efivar_work; extern struct work_struct efivar_work;
void efivar_run_worker(void); void efivar_run_worker(void);
......
...@@ -11,4 +11,8 @@ unsigned long ucs2_strlen(const ucs2_char_t *s); ...@@ -11,4 +11,8 @@ unsigned long ucs2_strlen(const ucs2_char_t *s);
unsigned long ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength); unsigned long ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength);
int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len); int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len);
unsigned long ucs2_utf8size(const ucs2_char_t *src);
unsigned long ucs2_as_utf8(u8 *dest, const ucs2_char_t *src,
unsigned long maxlength);
#endif /* _LINUX_UCS2_STRING_H_ */ #endif /* _LINUX_UCS2_STRING_H_ */
...@@ -49,3 +49,65 @@ ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len) ...@@ -49,3 +49,65 @@ ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len)
} }
} }
EXPORT_SYMBOL(ucs2_strncmp); EXPORT_SYMBOL(ucs2_strncmp);
unsigned long
ucs2_utf8size(const ucs2_char_t *src)
{
unsigned long i;
unsigned long j = 0;
for (i = 0; i < ucs2_strlen(src); i++) {
u16 c = src[i];
if (c >= 0x800)
j += 3;
else if (c >= 0x80)
j += 2;
else
j += 1;
}
return j;
}
EXPORT_SYMBOL(ucs2_utf8size);
/*
* copy at most maxlength bytes of whole utf8 characters to dest from the
* ucs2 string src.
*
* The return value is the number of characters copied, not including the
* final NUL character.
*/
unsigned long
ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength)
{
unsigned int i;
unsigned long j = 0;
unsigned long limit = ucs2_strnlen(src, maxlength);
for (i = 0; maxlength && i < limit; i++) {
u16 c = src[i];
if (c >= 0x800) {
if (maxlength < 3)
break;
maxlength -= 3;
dest[j++] = 0xe0 | (c & 0xf000) >> 12;
dest[j++] = 0x80 | (c & 0x0fc0) >> 6;
dest[j++] = 0x80 | (c & 0x003f);
} else if (c >= 0x80) {
if (maxlength < 2)
break;
maxlength -= 2;
dest[j++] = 0xc0 | (c & 0x7c0) >> 6;
dest[j++] = 0x80 | (c & 0x03f);
} else {
maxlength -= 1;
dest[j++] = c & 0x7f;
}
}
if (maxlength)
dest[j] = '\0';
return j;
}
EXPORT_SYMBOL(ucs2_as_utf8);
...@@ -88,7 +88,11 @@ test_delete() ...@@ -88,7 +88,11 @@ test_delete()
exit 1 exit 1
fi fi
rm $file 2>/dev/null
if [ $? -ne 0 ]; then
chattr -i $file
rm $file rm $file
fi
if [ -e $file ]; then if [ -e $file ]; then
echo "$file couldn't be deleted" >&2 echo "$file couldn't be deleted" >&2
...@@ -111,6 +115,7 @@ test_zero_size_delete() ...@@ -111,6 +115,7 @@ test_zero_size_delete()
exit 1 exit 1
fi fi
chattr -i $file
printf "$attrs" > $file printf "$attrs" > $file
if [ -e $file ]; then if [ -e $file ]; then
...@@ -141,8 +146,12 @@ test_valid_filenames() ...@@ -141,8 +146,12 @@ test_valid_filenames()
echo "$file could not be created" >&2 echo "$file could not be created" >&2
ret=1 ret=1
else else
rm $file 2>/dev/null
if [ $? -ne 0 ]; then
chattr -i $file
rm $file rm $file
fi fi
fi
done done
exit $ret exit $ret
...@@ -174,7 +183,11 @@ test_invalid_filenames() ...@@ -174,7 +183,11 @@ test_invalid_filenames()
if [ -e $file ]; then if [ -e $file ]; then
echo "Creating $file should have failed" >&2 echo "Creating $file should have failed" >&2
rm $file 2>/dev/null
if [ $? -ne 0 ]; then
chattr -i $file
rm $file rm $file
fi
ret=1 ret=1
fi fi
done done
......
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <linux/fs.h>
static int set_immutable(const char *path, int immutable)
{
unsigned int flags;
int fd;
int rc;
int error;
fd = open(path, O_RDONLY);
if (fd < 0)
return fd;
rc = ioctl(fd, FS_IOC_GETFLAGS, &flags);
if (rc < 0) {
error = errno;
close(fd);
errno = error;
return rc;
}
if (immutable)
flags |= FS_IMMUTABLE_FL;
else
flags &= ~FS_IMMUTABLE_FL;
rc = ioctl(fd, FS_IOC_SETFLAGS, &flags);
error = errno;
close(fd);
errno = error;
return rc;
}
static int get_immutable(const char *path)
{
unsigned int flags;
int fd;
int rc;
int error;
fd = open(path, O_RDONLY);
if (fd < 0)
return fd;
rc = ioctl(fd, FS_IOC_GETFLAGS, &flags);
if (rc < 0) {
error = errno;
close(fd);
errno = error;
return rc;
}
close(fd);
if (flags & FS_IMMUTABLE_FL)
return 1;
return 0;
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
...@@ -27,7 +85,7 @@ int main(int argc, char **argv) ...@@ -27,7 +85,7 @@ int main(int argc, char **argv)
buf[4] = 0; buf[4] = 0;
/* create a test variable */ /* create a test variable */
fd = open(path, O_WRONLY | O_CREAT); fd = open(path, O_WRONLY | O_CREAT, 0600);
if (fd < 0) { if (fd < 0) {
perror("open(O_WRONLY)"); perror("open(O_WRONLY)");
return EXIT_FAILURE; return EXIT_FAILURE;
...@@ -41,6 +99,18 @@ int main(int argc, char **argv) ...@@ -41,6 +99,18 @@ int main(int argc, char **argv)
close(fd); close(fd);
rc = get_immutable(path);
if (rc < 0) {
perror("ioctl(FS_IOC_GETFLAGS)");
return EXIT_FAILURE;
} else if (rc) {
rc = set_immutable(path, 0);
if (rc < 0) {
perror("ioctl(FS_IOC_SETFLAGS)");
return EXIT_FAILURE;
}
}
fd = open(path, O_RDONLY); fd = open(path, O_RDONLY);
if (fd < 0) { if (fd < 0) {
perror("open"); perror("open");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment