diff options
author | Bjorn Helgaas <bjorn.helgaas@hp.com> | 2006-01-08 04:04:13 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-01-08 23:14:02 -0500 |
commit | 80851ef2a5a404e6054211ca96ecd5ac4b06d297 (patch) | |
tree | dcacd2a475adc28c540b6012b58f1af9783778c1 | |
parent | 44ac8413901167589226abf824d994aa57e4fd28 (diff) |
[PATCH] /dev/mem: validate mmap requests
Add a hook so architectures can validate /dev/mem mmap requests.
This is analogous to validation we already perform in the read/write
paths.
The identity mapping scheme used on ia64 requires that each 16MB or
64MB granule be accessed with exactly one attribute (write-back or
uncacheable). This avoids "attribute aliasing", which can cause a
machine check.
Sample problem scenario:
- Machine supports VGA, so it has uncacheable (UC) MMIO at 640K-768K
- efi_memmap_init() discards any write-back (WB) memory in the first granule
- Application (e.g., "hwinfo") mmaps /dev/mem, offset 0
- hwinfo receives UC mapping (the default, since memmap says "no WB here")
- Machine check abort (on chipsets that don't support UC access to WB
memory, e.g., sx1000)
In the scenario above, the only choices are
- Use WB for hwinfo mmap. Can't do this because it causes attribute
aliasing with the UC mapping for the VGA MMIO space.
- Use UC for hwinfo mmap. Can't do this because the chipset may not
support UC for that region.
- Disallow the hwinfo mmap with -EINVAL. That's what this patch does.
Signed-off-by: Bjorn Helgaas <bjorn.helgaas@hp.com>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: "Luck, Tony" <tony.luck@intel.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r-- | arch/ia64/kernel/efi.c | 160 | ||||
-rw-r--r-- | drivers/char/mem.c | 14 | ||||
-rw-r--r-- | include/asm-ia64/io.h | 1 |
3 files changed, 124 insertions, 51 deletions
diff --git a/arch/ia64/kernel/efi.c b/arch/ia64/kernel/efi.c index a3aa45cbcfa0..c485a3b32ba8 100644 --- a/arch/ia64/kernel/efi.c +++ b/arch/ia64/kernel/efi.c | |||
@@ -247,6 +247,32 @@ typedef struct kern_memdesc { | |||
247 | 247 | ||
248 | static kern_memdesc_t *kern_memmap; | 248 | static kern_memdesc_t *kern_memmap; |
249 | 249 | ||
250 | #define efi_md_size(md) (md->num_pages << EFI_PAGE_SHIFT) | ||
251 | |||
252 | static inline u64 | ||
253 | kmd_end(kern_memdesc_t *kmd) | ||
254 | { | ||
255 | return (kmd->start + (kmd->num_pages << EFI_PAGE_SHIFT)); | ||
256 | } | ||
257 | |||
258 | static inline u64 | ||
259 | efi_md_end(efi_memory_desc_t *md) | ||
260 | { | ||
261 | return (md->phys_addr + efi_md_size(md)); | ||
262 | } | ||
263 | |||
264 | static inline int | ||
265 | efi_wb(efi_memory_desc_t *md) | ||
266 | { | ||
267 | return (md->attribute & EFI_MEMORY_WB); | ||
268 | } | ||
269 | |||
270 | static inline int | ||
271 | efi_uc(efi_memory_desc_t *md) | ||
272 | { | ||
273 | return (md->attribute & EFI_MEMORY_UC); | ||
274 | } | ||
275 | |||
250 | static void | 276 | static void |
251 | walk (efi_freemem_callback_t callback, void *arg, u64 attr) | 277 | walk (efi_freemem_callback_t callback, void *arg, u64 attr) |
252 | { | 278 | { |
@@ -595,8 +621,8 @@ efi_get_iobase (void) | |||
595 | return 0; | 621 | return 0; |
596 | } | 622 | } |
597 | 623 | ||
598 | u32 | 624 | static efi_memory_desc_t * |
599 | efi_mem_type (unsigned long phys_addr) | 625 | efi_memory_descriptor (unsigned long phys_addr) |
600 | { | 626 | { |
601 | void *efi_map_start, *efi_map_end, *p; | 627 | void *efi_map_start, *efi_map_end, *p; |
602 | efi_memory_desc_t *md; | 628 | efi_memory_desc_t *md; |
@@ -610,13 +636,13 @@ efi_mem_type (unsigned long phys_addr) | |||
610 | md = p; | 636 | md = p; |
611 | 637 | ||
612 | if (phys_addr - md->phys_addr < (md->num_pages << EFI_PAGE_SHIFT)) | 638 | if (phys_addr - md->phys_addr < (md->num_pages << EFI_PAGE_SHIFT)) |
613 | return md->type; | 639 | return md; |
614 | } | 640 | } |
615 | return 0; | 641 | return 0; |
616 | } | 642 | } |
617 | 643 | ||
618 | u64 | 644 | static int |
619 | efi_mem_attributes (unsigned long phys_addr) | 645 | efi_memmap_has_mmio (void) |
620 | { | 646 | { |
621 | void *efi_map_start, *efi_map_end, *p; | 647 | void *efi_map_start, *efi_map_end, *p; |
622 | efi_memory_desc_t *md; | 648 | efi_memory_desc_t *md; |
@@ -629,36 +655,98 @@ efi_mem_attributes (unsigned long phys_addr) | |||
629 | for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { | 655 | for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { |
630 | md = p; | 656 | md = p; |
631 | 657 | ||
632 | if (phys_addr - md->phys_addr < (md->num_pages << EFI_PAGE_SHIFT)) | 658 | if (md->type == EFI_MEMORY_MAPPED_IO) |
633 | return md->attribute; | 659 | return 1; |
634 | } | 660 | } |
635 | return 0; | 661 | return 0; |
636 | } | 662 | } |
663 | |||
664 | u32 | ||
665 | efi_mem_type (unsigned long phys_addr) | ||
666 | { | ||
667 | efi_memory_desc_t *md = efi_memory_descriptor(phys_addr); | ||
668 | |||
669 | if (md) | ||
670 | return md->type; | ||
671 | return 0; | ||
672 | } | ||
673 | |||
674 | u64 | ||
675 | efi_mem_attributes (unsigned long phys_addr) | ||
676 | { | ||
677 | efi_memory_desc_t *md = efi_memory_descriptor(phys_addr); | ||
678 | |||
679 | if (md) | ||
680 | return md->attribute; | ||
681 | return 0; | ||
682 | } | ||
637 | EXPORT_SYMBOL(efi_mem_attributes); | 683 | EXPORT_SYMBOL(efi_mem_attributes); |
638 | 684 | ||
685 | /* | ||
686 | * Determines whether the memory at phys_addr supports the desired | ||
687 | * attribute (WB, UC, etc). If this returns 1, the caller can safely | ||
688 | * access *size bytes at phys_addr with the specified attribute. | ||
689 | */ | ||
690 | static int | ||
691 | efi_mem_attribute_range (unsigned long phys_addr, unsigned long *size, u64 attr) | ||
692 | { | ||
693 | efi_memory_desc_t *md = efi_memory_descriptor(phys_addr); | ||
694 | unsigned long md_end; | ||
695 | |||
696 | if (!md || (md->attribute & attr) != attr) | ||
697 | return 0; | ||
698 | |||
699 | do { | ||
700 | md_end = efi_md_end(md); | ||
701 | if (phys_addr + *size <= md_end) | ||
702 | return 1; | ||
703 | |||
704 | md = efi_memory_descriptor(md_end); | ||
705 | if (!md || (md->attribute & attr) != attr) { | ||
706 | *size = md_end - phys_addr; | ||
707 | return 1; | ||
708 | } | ||
709 | } while (md); | ||
710 | return 0; | ||
711 | } | ||
712 | |||
713 | /* | ||
714 | * For /dev/mem, we only allow read & write system calls to access | ||
715 | * write-back memory, because read & write don't allow the user to | ||
716 | * control access size. | ||
717 | */ | ||
639 | int | 718 | int |
640 | valid_phys_addr_range (unsigned long phys_addr, unsigned long *size) | 719 | valid_phys_addr_range (unsigned long phys_addr, unsigned long *size) |
641 | { | 720 | { |
642 | void *efi_map_start, *efi_map_end, *p; | 721 | return efi_mem_attribute_range(phys_addr, size, EFI_MEMORY_WB); |
643 | efi_memory_desc_t *md; | 722 | } |
644 | u64 efi_desc_size; | ||
645 | 723 | ||
646 | efi_map_start = __va(ia64_boot_param->efi_memmap); | 724 | /* |
647 | efi_map_end = efi_map_start + ia64_boot_param->efi_memmap_size; | 725 | * We allow mmap of anything in the EFI memory map that supports |
648 | efi_desc_size = ia64_boot_param->efi_memdesc_size; | 726 | * either write-back or uncacheable access. For uncacheable regions, |
727 | * the supported access sizes are system-dependent, and the user is | ||
728 | * responsible for using the correct size. | ||
729 | * | ||
730 | * Note that this doesn't currently allow access to hot-added memory, | ||
731 | * because that doesn't appear in the boot-time EFI memory map. | ||
732 | */ | ||
733 | int | ||
734 | valid_mmap_phys_addr_range (unsigned long phys_addr, unsigned long *size) | ||
735 | { | ||
736 | if (efi_mem_attribute_range(phys_addr, size, EFI_MEMORY_WB)) | ||
737 | return 1; | ||
649 | 738 | ||
650 | for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { | 739 | if (efi_mem_attribute_range(phys_addr, size, EFI_MEMORY_UC)) |
651 | md = p; | 740 | return 1; |
652 | 741 | ||
653 | if (phys_addr - md->phys_addr < (md->num_pages << EFI_PAGE_SHIFT)) { | 742 | /* |
654 | if (!(md->attribute & EFI_MEMORY_WB)) | 743 | * Some firmware doesn't report MMIO regions in the EFI memory map. |
655 | return 0; | 744 | * The Intel BigSur (a.k.a. HP i2000) has this problem. In this |
745 | * case, we can't use the EFI memory map to validate mmap requests. | ||
746 | */ | ||
747 | if (!efi_memmap_has_mmio()) | ||
748 | return 1; | ||
656 | 749 | ||
657 | if (*size > md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - phys_addr) | ||
658 | *size = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - phys_addr; | ||
659 | return 1; | ||
660 | } | ||
661 | } | ||
662 | return 0; | 750 | return 0; |
663 | } | 751 | } |
664 | 752 | ||
@@ -707,32 +795,6 @@ efi_uart_console_only(void) | |||
707 | return 0; | 795 | return 0; |
708 | } | 796 | } |
709 | 797 | ||
710 | #define efi_md_size(md) (md->num_pages << EFI_PAGE_SHIFT) | ||
711 | |||
712 | static inline u64 | ||
713 | kmd_end(kern_memdesc_t *kmd) | ||
714 | { | ||
715 | return (kmd->start + (kmd->num_pages << EFI_PAGE_SHIFT)); | ||
716 | } | ||
717 | |||
718 | static inline u64 | ||
719 | efi_md_end(efi_memory_desc_t *md) | ||
720 | { | ||
721 | return (md->phys_addr + efi_md_size(md)); | ||
722 | } | ||
723 | |||
724 | static inline int | ||
725 | efi_wb(efi_memory_desc_t *md) | ||
726 | { | ||
727 | return (md->attribute & EFI_MEMORY_WB); | ||
728 | } | ||
729 | |||
730 | static inline int | ||
731 | efi_uc(efi_memory_desc_t *md) | ||
732 | { | ||
733 | return (md->attribute & EFI_MEMORY_UC); | ||
734 | } | ||
735 | |||
736 | /* | 798 | /* |
737 | * Look for the first granule aligned memory descriptor memory | 799 | * Look for the first granule aligned memory descriptor memory |
738 | * that is big enough to hold EFI memory map. Make sure this | 800 | * that is big enough to hold EFI memory map. Make sure this |
diff --git a/drivers/char/mem.c b/drivers/char/mem.c index ce3ff8641191..5b2d18035073 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c | |||
@@ -101,6 +101,11 @@ static inline int valid_phys_addr_range(unsigned long addr, size_t *count) | |||
101 | 101 | ||
102 | return 1; | 102 | return 1; |
103 | } | 103 | } |
104 | |||
105 | static inline int valid_mmap_phys_addr_range(unsigned long addr, size_t *size) | ||
106 | { | ||
107 | return 1; | ||
108 | } | ||
104 | #endif | 109 | #endif |
105 | 110 | ||
106 | /* | 111 | /* |
@@ -244,15 +249,20 @@ static pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn, | |||
244 | 249 | ||
245 | static int mmap_mem(struct file * file, struct vm_area_struct * vma) | 250 | static int mmap_mem(struct file * file, struct vm_area_struct * vma) |
246 | { | 251 | { |
252 | size_t size = vma->vm_end - vma->vm_start; | ||
253 | |||
254 | if (!valid_mmap_phys_addr_range(vma->vm_pgoff << PAGE_SHIFT, &size)) | ||
255 | return -EINVAL; | ||
256 | |||
247 | vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff, | 257 | vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff, |
248 | vma->vm_end - vma->vm_start, | 258 | size, |
249 | vma->vm_page_prot); | 259 | vma->vm_page_prot); |
250 | 260 | ||
251 | /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */ | 261 | /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */ |
252 | if (remap_pfn_range(vma, | 262 | if (remap_pfn_range(vma, |
253 | vma->vm_start, | 263 | vma->vm_start, |
254 | vma->vm_pgoff, | 264 | vma->vm_pgoff, |
255 | vma->vm_end-vma->vm_start, | 265 | size, |
256 | vma->vm_page_prot)) | 266 | vma->vm_page_prot)) |
257 | return -EAGAIN; | 267 | return -EAGAIN; |
258 | return 0; | 268 | return 0; |
diff --git a/include/asm-ia64/io.h b/include/asm-ia64/io.h index cf772a67f858..b64fdb985494 100644 --- a/include/asm-ia64/io.h +++ b/include/asm-ia64/io.h | |||
@@ -89,6 +89,7 @@ phys_to_virt (unsigned long address) | |||
89 | 89 | ||
90 | #define ARCH_HAS_VALID_PHYS_ADDR_RANGE | 90 | #define ARCH_HAS_VALID_PHYS_ADDR_RANGE |
91 | extern int valid_phys_addr_range (unsigned long addr, size_t *count); /* efi.c */ | 91 | extern int valid_phys_addr_range (unsigned long addr, size_t *count); /* efi.c */ |
92 | extern int valid_mmap_phys_addr_range (unsigned long addr, size_t *count); | ||
92 | 93 | ||
93 | /* | 94 | /* |
94 | * The following two macros are deprecated and scheduled for removal. | 95 | * The following two macros are deprecated and scheduled for removal. |