drivers/rtc/rtc-isl1208.c: call rtc_update_irq() from the alarm irq handler
[pandora-kernel.git] / drivers / firmware / efivars.c
index 8370f72..5d5a868 100644 (file)
@@ -191,6 +191,190 @@ utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
        }
 }
 
+static bool
+validate_device_path(struct efi_variable *var, int match, u8 *buffer,
+                    unsigned long len)
+{
+       struct efi_generic_dev_path *node;
+       int offset = 0;
+
+       node = (struct efi_generic_dev_path *)buffer;
+
+       if (len < sizeof(*node))
+               return false;
+
+       while (offset <= len - sizeof(*node) &&
+              node->length >= sizeof(*node) &&
+               node->length <= len - offset) {
+               offset += node->length;
+
+               if ((node->type == EFI_DEV_END_PATH ||
+                    node->type == EFI_DEV_END_PATH2) &&
+                   node->sub_type == EFI_DEV_END_ENTIRE)
+                       return true;
+
+               node = (struct efi_generic_dev_path *)(buffer + offset);
+       }
+
+       /*
+        * If we're here then either node->length pointed past the end
+        * of the buffer or we reached the end of the buffer without
+        * finding a device path end node.
+        */
+       return false;
+}
+
+static bool
+validate_boot_order(struct efi_variable *var, int match, u8 *buffer,
+                   unsigned long len)
+{
+       /* An array of 16-bit integers */
+       if ((len % 2) != 0)
+               return false;
+
+       return true;
+}
+
+static bool
+validate_load_option(struct efi_variable *var, int match, u8 *buffer,
+                    unsigned long len)
+{
+       u16 filepathlength;
+       int i, desclength = 0, namelen;
+
+       namelen = utf16_strnlen(var->VariableName, sizeof(var->VariableName));
+
+       /* Either "Boot" or "Driver" followed by four digits of hex */
+       for (i = match; i < match+4; i++) {
+               if (var->VariableName[i] > 127 ||
+                   hex_to_bin(var->VariableName[i] & 0xff) < 0)
+                       return true;
+       }
+
+       /* Reject it if there's 4 digits of hex and then further content */
+       if (namelen > match + 4)
+               return false;
+
+       /* A valid entry must be at least 8 bytes */
+       if (len < 8)
+               return false;
+
+       filepathlength = buffer[4] | buffer[5] << 8;
+
+       /*
+        * There's no stored length for the description, so it has to be
+        * found by hand
+        */
+       desclength = utf16_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
+
+       /* Each boot entry must have a descriptor */
+       if (!desclength)
+               return false;
+
+       /*
+        * If the sum of the length of the description, the claimed filepath
+        * length and the original header are greater than the length of the
+        * variable, it's malformed
+        */
+       if ((desclength + filepathlength + 6) > len)
+               return false;
+
+       /*
+        * And, finally, check the filepath
+        */
+       return validate_device_path(var, match, buffer + desclength + 6,
+                                   filepathlength);
+}
+
+static bool
+validate_uint16(struct efi_variable *var, int match, u8 *buffer,
+               unsigned long len)
+{
+       /* A single 16-bit integer */
+       if (len != 2)
+               return false;
+
+       return true;
+}
+
+static bool
+validate_ascii_string(struct efi_variable *var, int match, u8 *buffer,
+                     unsigned long len)
+{
+       int i;
+
+       for (i = 0; i < len; i++) {
+               if (buffer[i] > 127)
+                       return false;
+
+               if (buffer[i] == 0)
+                       return true;
+       }
+
+       return false;
+}
+
+struct variable_validate {
+       char *name;
+       bool (*validate)(struct efi_variable *var, int match, u8 *data,
+                        unsigned long len);
+};
+
+static const struct variable_validate variable_validate[] = {
+       { "BootNext", validate_uint16 },
+       { "BootOrder", validate_boot_order },
+       { "DriverOrder", validate_boot_order },
+       { "Boot*", validate_load_option },
+       { "Driver*", validate_load_option },
+       { "ConIn", validate_device_path },
+       { "ConInDev", validate_device_path },
+       { "ConOut", validate_device_path },
+       { "ConOutDev", validate_device_path },
+       { "ErrOut", validate_device_path },
+       { "ErrOutDev", validate_device_path },
+       { "Timeout", validate_uint16 },
+       { "Lang", validate_ascii_string },
+       { "PlatformLang", validate_ascii_string },
+       { "", NULL },
+};
+
+static bool
+validate_var(struct efi_variable *var, u8 *data, unsigned long len)
+{
+       int i;
+       u16 *unicode_name = var->VariableName;
+
+       for (i = 0; variable_validate[i].validate != NULL; i++) {
+               const char *name = variable_validate[i].name;
+               int match;
+
+               for (match = 0; ; match++) {
+                       char c = name[match];
+                       u16 u = unicode_name[match];
+
+                       /* All special variables are plain ascii */
+                       if (u > 127)
+                               return true;
+
+                       /* Wildcard in the matching name means we've matched */
+                       if (c == '*')
+                               return variable_validate[i].validate(var,
+                                                            match, data, len);
+
+                       /* Case sensitive match */
+                       if (c != u)
+                               break;
+
+                       /* Reached the end of the string while matching */
+                       if (!c)
+                               return variable_validate[i].validate(var,
+                                                            match, data, len);
+               }
+       }
+
+       return true;
+}
+
 static efi_status_t
 get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
 {
@@ -251,12 +435,23 @@ efivar_attr_read(struct efivar_entry *entry, char *buf)
        if (status != EFI_SUCCESS)
                return -EIO;
 
-       if (var->Attributes & 0x1)
+       if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
                str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
-       if (var->Attributes & 0x2)
+       if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
                str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
-       if (var->Attributes & 0x4)
+       if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
                str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
+       if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
+               str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
+       if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
+               str += sprintf(str,
+                       "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
+       if (var->Attributes &
+                       EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
+               str += sprintf(str,
+                       "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
+       if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
+               str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
        return str - buf;
 }
 
@@ -324,6 +519,12 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
                return -EINVAL;
        }
 
+       if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
+           validate_var(new_var, new_var->Data, new_var->DataSize) == false) {
+               printk(KERN_ERR "efivars: Malformed variable content\n");
+               return -EINVAL;
+       }
+
        spin_lock(&efivars->lock);
        status = efivars->ops->set_variable(new_var->VariableName,
                                            &new_var->VendorGuid,
@@ -457,7 +658,8 @@ static int efi_pstore_close(struct pstore_info *psi)
 }
 
 static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
-                              struct timespec *timespec, struct pstore_info *psi)
+                              struct timespec *timespec,
+                              char **buf, struct pstore_info *psi)
 {
        efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
        struct efivars *efivars = psi->data;
@@ -478,7 +680,11 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
                                timespec->tv_nsec = 0;
                                get_var_data_locked(efivars, &efivars->walk_entry->var);
                                size = efivars->walk_entry->var.DataSize;
-                               memcpy(psi->buf, efivars->walk_entry->var.Data, size);
+                               *buf = kmalloc(size, GFP_KERNEL);
+                               if (*buf == NULL)
+                                       return -ENOMEM;
+                               memcpy(*buf, efivars->walk_entry->var.Data,
+                                      size);
                                efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
                                                   struct efivar_entry, list);
                                return size;
@@ -576,7 +782,8 @@ static int efi_pstore_close(struct pstore_info *psi)
 }
 
 static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
-                              struct timespec *time, struct pstore_info *psi)
+                              struct timespec *timespec,
+                              char **buf, struct pstore_info *psi)
 {
        return -1;
 }
@@ -618,6 +825,12 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
 
+       if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
+           validate_var(new_var, new_var->Data, new_var->DataSize) == false) {
+               printk(KERN_ERR "efivars: Malformed variable content\n");
+               return -EINVAL;
+       }
+
        spin_lock(&efivars->lock);
 
        /*
@@ -1009,7 +1222,7 @@ efivars_init(void)
        printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
               EFIVARS_DATE);
 
-       if (!efi_enabled)
+       if (!efi_enabled(EFI_RUNTIME_SERVICES))
                return 0;
 
        /* For now we'll register the efi directory at /sys/firmware/efi */
@@ -1047,7 +1260,7 @@ err_put:
 static void __exit
 efivars_exit(void)
 {
-       if (efi_enabled) {
+       if (efi_enabled(EFI_RUNTIME_SERVICES)) {
                unregister_efivars(&__efivars);
                kobject_put(efi_kobj);
        }