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

s390/kaslr: generalize and improve random base distribution

Improve the distribution algorithm of random base address to ensure
a uniformity among all suitable addresses. To generate a random value
once, and to build a continuous range in which every value is suitable,
count all the suitable addresses (referred to as positions) that can be
used as a base address. The positions are counted by iterating over the
usable memory ranges. For each range that is big enough to accommodate
the image, count all the suitable addresses where the image can be placed,
while taking reserved memory ranges into consideration.

A new function "iterate_valid_positions()" has dual purpose. Firstly, it
is called to count the positions in a given memory range, and secondly,
to convert a random position back to an address.

"get_random_base()" has been replaced with more generic
"randomize_within_range()" which now could be called for randomizing
base addresses not just for the kernel image.

Acked-by: Alexander Gordeev <agordeev@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>

+112 -11
+3 -1
arch/s390/boot/boot.h
··· 54 54 unsigned long physmem_alloc_range(enum reserved_range_type type, unsigned long size, 55 55 unsigned long align, unsigned long min, unsigned long max, 56 56 bool die_on_oom); 57 + unsigned long get_physmem_alloc_pos(void); 57 58 bool ipl_report_certs_intersects(unsigned long addr, unsigned long size, 58 59 unsigned long *intersection_start); 59 60 bool is_ipl_block_dump(void); ··· 67 66 void print_missing_facilities(void); 68 67 void sclp_early_setup_buffer(void); 69 68 void print_pgm_check_info(void); 70 - unsigned long get_random_base(void); 69 + unsigned long randomize_within_range(unsigned long size, unsigned long align, 70 + unsigned long min, unsigned long max); 71 71 void setup_vmem(unsigned long asce_limit); 72 72 void __printf(1, 2) decompressor_printk(const char *fmt, ...); 73 73 void print_stacktrace(unsigned long sp);
+101 -9
arch/s390/boot/kaslr.c
··· 91 91 return 0; 92 92 } 93 93 94 - unsigned long get_random_base(void) 94 + static void sort_reserved_ranges(struct reserved_range *res, unsigned long size) 95 95 { 96 - unsigned long vmlinux_size = vmlinux.image_size + vmlinux.bss_size; 97 - unsigned long minimal_pos = vmlinux.default_lma + vmlinux_size; 98 - unsigned long random; 96 + struct reserved_range tmp; 97 + int i, j; 99 98 100 - /* [vmlinux.default_lma + vmlinux.image_size + vmlinux.bss_size : physmem_info.usable] */ 101 - if (get_random(physmem_info.usable - minimal_pos, &random)) 99 + for (i = 1; i < size; i++) { 100 + tmp = res[i]; 101 + for (j = i - 1; j >= 0 && res[j].start > tmp.start; j--) 102 + res[j + 1] = res[j]; 103 + res[j + 1] = tmp; 104 + } 105 + } 106 + 107 + static unsigned long iterate_valid_positions(unsigned long size, unsigned long align, 108 + unsigned long _min, unsigned long _max, 109 + struct reserved_range *res, size_t res_count, 110 + bool pos_count, unsigned long find_pos) 111 + { 112 + unsigned long start, end, tmp_end, range_pos, pos = 0; 113 + struct reserved_range *res_end = res + res_count; 114 + struct reserved_range *skip_res; 115 + int i; 116 + 117 + align = max(align, 8UL); 118 + _min = round_up(_min, align); 119 + for_each_physmem_usable_range(i, &start, &end) { 120 + if (_min >= end) 121 + continue; 122 + start = round_up(start, align); 123 + if (start >= _max) 124 + break; 125 + start = max(_min, start); 126 + end = min(_max, end); 127 + 128 + while (start + size <= end) { 129 + /* skip reserved ranges below the start */ 130 + while (res && res->end <= start) { 131 + res++; 132 + if (res >= res_end) 133 + res = NULL; 134 + } 135 + skip_res = NULL; 136 + tmp_end = end; 137 + /* has intersecting reserved range */ 138 + if (res && res->start < end) { 139 + skip_res = res; 140 + tmp_end = res->start; 141 + } 142 + if (start + size <= tmp_end) { 143 + range_pos = (tmp_end - start - size) / align + 1; 144 + if (pos_count) { 145 + pos += range_pos; 146 + } else { 147 + if (range_pos >= find_pos) 148 + return start + (find_pos - 1) * align; 149 + find_pos -= range_pos; 150 + } 151 + } 152 + if (!skip_res) 153 + break; 154 + start = round_up(skip_res->end, align); 155 + } 156 + } 157 + 158 + return pos_count ? pos : 0; 159 + } 160 + 161 + /* 162 + * Two types of decompressor memory allocations/reserves are considered 163 + * differently. 164 + * 165 + * "Static" or "single" allocations are done via physmem_alloc_range() and 166 + * physmem_reserve(), and they are listed in physmem_info.reserved[]. Each 167 + * type of "static" allocation can only have one allocation per type and 168 + * cannot have chains. 169 + * 170 + * On the other hand, "dynamic" or "repetitive" allocations are done via 171 + * physmem_alloc_top_down(). These allocations are tightly packed together 172 + * top down from the end of online memory. physmem_alloc_pos represents 173 + * current position where those allocations start. 174 + * 175 + * Functions randomize_within_range() and iterate_valid_positions() 176 + * only consider "dynamic" allocations by never looking above 177 + * physmem_alloc_pos. "Static" allocations, however, are explicitly 178 + * considered by checking the "res" (reserves) array. The first 179 + * reserved_range of a "dynamic" allocation may also be checked along the 180 + * way, but it will always be above the maximum value anyway. 181 + */ 182 + unsigned long randomize_within_range(unsigned long size, unsigned long align, 183 + unsigned long min, unsigned long max) 184 + { 185 + struct reserved_range res[RR_MAX]; 186 + unsigned long max_pos, pos; 187 + 188 + memcpy(res, physmem_info.reserved, sizeof(res)); 189 + sort_reserved_ranges(res, ARRAY_SIZE(res)); 190 + max = min(max, get_physmem_alloc_pos()); 191 + 192 + max_pos = iterate_valid_positions(size, align, min, max, res, ARRAY_SIZE(res), true, 0); 193 + if (!max_pos) 102 194 return 0; 103 - 104 - return physmem_alloc_range(RR_VMLINUX, vmlinux_size, THREAD_SIZE, 105 - vmlinux.default_lma, minimal_pos + random, false); 195 + if (get_random(max_pos, &pos)) 196 + return 0; 197 + return iterate_valid_positions(size, align, min, max, res, ARRAY_SIZE(res), false, pos + 1); 106 198 }
+5
arch/s390/boot/physmem_info.c
··· 321 321 physmem_alloc_ranges = ranges_left; 322 322 return addr; 323 323 } 324 + 325 + unsigned long get_physmem_alloc_pos(void) 326 + { 327 + return physmem_alloc_pos; 328 + }
+3 -1
arch/s390/boot/startup.c
··· 316 316 rescue_initrd(safe_addr, ident_map_size); 317 317 318 318 if (kaslr_enabled()) { 319 - vmlinux_lma = get_random_base(); 319 + vmlinux_lma = randomize_within_range(vmlinux.image_size + vmlinux.bss_size, 320 + THREAD_SIZE, vmlinux.default_lma, 321 + ident_map_size); 320 322 if (vmlinux_lma) { 321 323 __kaslr_offset = vmlinux_lma - vmlinux.default_lma; 322 324 offset_vmlinux_info(__kaslr_offset);