Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

mtdchar: fix offset overflow detection

Sasha Levin has been running trinity in a KVM tools guest, and was able
to trigger the BUG_ON() at arch/x86/mm/pat.c:279 (verifying the range of
the memory type). The call trace showed that it was mtdchar_mmap() that
created an invalid remap_pfn_range().

The problem is that mtdchar_mmap() does various really odd and subtle
things with the vma page offset etc, and uses the wrong types (and the
wrong overflow) detection for it.

For example, the page offset may well be 32-bit on a 32-bit
architecture, but after shifting it up by PAGE_SHIFT, we need to use a
potentially 64-bit resource_size_t to correctly hold the full value.

Also, we need to check that the vma length plus offset doesn't overflow
before we check that it is smaller than the length of the mtdmap region.

This fixes things up and tries to make the code a bit easier to read.

Reported-and-tested-by: Sasha Levin <levinsasha928@gmail.com>
Acked-by: Suresh Siddha <suresh.b.siddha@intel.com>
Acked-by: Artem Bityutskiy <dedekind1@gmail.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: linux-mtd@lists.infradead.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

+42 -6
+42 -6
drivers/mtd/mtdchar.c
··· 1123 1123 } 1124 1124 #endif 1125 1125 1126 + static inline unsigned long get_vm_size(struct vm_area_struct *vma) 1127 + { 1128 + return vma->vm_end - vma->vm_start; 1129 + } 1130 + 1131 + static inline resource_size_t get_vm_offset(struct vm_area_struct *vma) 1132 + { 1133 + return (resource_size_t) vma->vm_pgoff << PAGE_SHIFT; 1134 + } 1135 + 1136 + /* 1137 + * Set a new vm offset. 1138 + * 1139 + * Verify that the incoming offset really works as a page offset, 1140 + * and that the offset and size fit in a resource_size_t. 1141 + */ 1142 + static inline int set_vm_offset(struct vm_area_struct *vma, resource_size_t off) 1143 + { 1144 + pgoff_t pgoff = off >> PAGE_SHIFT; 1145 + if (off != (resource_size_t) pgoff << PAGE_SHIFT) 1146 + return -EINVAL; 1147 + if (off + get_vm_size(vma) - 1 < off) 1148 + return -EINVAL; 1149 + vma->vm_pgoff = pgoff; 1150 + return 0; 1151 + } 1152 + 1126 1153 /* 1127 1154 * set up a mapping for shared memory segments 1128 1155 */ ··· 1159 1132 struct mtd_file_info *mfi = file->private_data; 1160 1133 struct mtd_info *mtd = mfi->mtd; 1161 1134 struct map_info *map = mtd->priv; 1162 - unsigned long start; 1163 - unsigned long off; 1164 - u32 len; 1135 + resource_size_t start, off; 1136 + unsigned long len, vma_len; 1165 1137 1166 1138 if (mtd->type == MTD_RAM || mtd->type == MTD_ROM) { 1167 - off = vma->vm_pgoff << PAGE_SHIFT; 1139 + off = get_vm_offset(vma); 1168 1140 start = map->phys; 1169 1141 len = PAGE_ALIGN((start & ~PAGE_MASK) + map->size); 1170 1142 start &= PAGE_MASK; 1171 - if ((vma->vm_end - vma->vm_start + off) > len) 1143 + vma_len = get_vm_size(vma); 1144 + 1145 + /* Overflow in off+len? */ 1146 + if (vma_len + off < off) 1147 + return -EINVAL; 1148 + /* Does it fit in the mapping? */ 1149 + if (vma_len + off > len) 1172 1150 return -EINVAL; 1173 1151 1174 1152 off += start; 1175 - vma->vm_pgoff = off >> PAGE_SHIFT; 1153 + /* Did that overflow? */ 1154 + if (off < start) 1155 + return -EINVAL; 1156 + if (set_vm_offset(vma, off) < 0) 1157 + return -EINVAL; 1176 1158 vma->vm_flags |= VM_IO | VM_RESERVED; 1177 1159 1178 1160 #ifdef pgprot_noncached