Re: [PATCH v4] x86: fix kaslr and memmap collision

From: Dave Jiang
Date: Wed Jan 04 2017 - 12:07:15 EST


On 01/03/2017 07:37 PM, Baoquan He wrote:
> Hi Dave,
>
> I have several concerns, please see the inline comments.
>
> On 01/03/17 at 01:48pm, Dave Jiang wrote:
>> CONFIG_RANDOMIZE_BASE relocates the kernel to a random base address.
>> However it does not take into account the memmap= parameter passed in from
>> the kernel cmdline. This results in the kernel sometimes being put in
>> the middle of the user memmap. Teaching kaslr to not insert the kernel in
> ~~~~~~~~~~~
> It could be better to be replaced with "user-defined physical RAM map"
> or memmap directly because memmap is meaning user-defined physical RAM
> map. Please check the boot info printing about them. I was confused at
> first glance.

Will update

>> memmap defined regions. We will support up to 4 memmap regions. Any
>> additional regions will cause kaslr to disable. The mem_avoid set has
>> been augmented to add up to 4 regions of memmaps provided by the user
> ~~~
> 4 un-usable memmap region need be cared, usable memory is not
> concerned

Will update

.
>> to exclude those regions from the set of valid address range to insert
>> the uncompressed kernel image. The nn@ss ranges will be skipped by the
>> mem_avoid set since it indicates memory useable.
>>
>> Signed-off-by: Dave Jiang <dave.jiang@xxxxxxxxx>
>> ---
>> arch/x86/boot/boot.h | 3 +
>> arch/x86/boot/compressed/kaslr.c | 131 ++++++++++++++++++++++++++++++++++++++
>> arch/x86/boot/string.c | 38 +++++++++++
>> 3 files changed, 172 insertions(+)
>>
>> diff --git a/arch/x86/boot/boot.h b/arch/x86/boot/boot.h
>> index e5612f3..59c2075 100644
>> --- a/arch/x86/boot/boot.h
>> +++ b/arch/x86/boot/boot.h
>> @@ -332,7 +332,10 @@ int strncmp(const char *cs, const char *ct, size_t count);
>> size_t strnlen(const char *s, size_t maxlen);
>> unsigned int atou(const char *s);
>> unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base);
>> +unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base);
>> +long simple_strtol(const char *cp, char **endp, unsigned int base);
>> size_t strlen(const char *s);
>> +char *strchr(const char *s, int c);
>>
>> /* tty.c */
>> void puts(const char *);
>> diff --git a/arch/x86/boot/compressed/kaslr.c b/arch/x86/boot/compressed/kaslr.c
>> index a66854d..f5a1c8d 100644
>> --- a/arch/x86/boot/compressed/kaslr.c
>> +++ b/arch/x86/boot/compressed/kaslr.c
>> @@ -11,6 +11,7 @@
>> */
>> #include "misc.h"
>> #include "error.h"
>> +#include "../boot.h"
>>
>> #include <generated/compile.h>
>> #include <linux/module.h>
>> @@ -61,9 +62,16 @@ enum mem_avoid_index {
>> MEM_AVOID_INITRD,
>> MEM_AVOID_CMDLINE,
>> MEM_AVOID_BOOTPARAMS,
>> + MEM_AVOID_MEMMAP1,
>> + MEM_AVOID_MEMMAP2,
>> + MEM_AVOID_MEMMAP3,
>> + MEM_AVOID_MEMMAP4,
>
> This looks not good. Could it be done like fixed_addresses?
> Something like:
>
> MEM_AVOID_MEMMAP_BEGIN,
> MEM_AVOID_MEMMAP_BEGIN + MEM_AVOID_MAX -1,
>
> Please point it out to me if there's some existing code in kernel like
> your way, I can also accept it.

I think you mean:
MEM_AVOID_MEMMAP_BEGIN + MAX_MEMMAP_REGIONS - 1

I will change


>
>> MEM_AVOID_MAX,
>> };
>>
>> +/* only supporting at most 4 memmap regions with kaslr */
> And here, "Only supporting at most 4 un-usable memmap regions with kaslr"?
> Surely this is based on if you will ignore the usable memory and do not
> store it as 0. And also the log need be changed too accordingly.
>> +#define MAX_MEMMAP_REGIONS 4
>> +
>> static struct mem_vector mem_avoid[MEM_AVOID_MAX];
>>
>> static bool mem_overlaps(struct mem_vector *one, struct mem_vector *two)
>> @@ -77,6 +85,121 @@ static bool mem_overlaps(struct mem_vector *one, struct mem_vector *two)
>> return true;
>> }
>>
>> +/**
>> + * _memparse - parse a string with mem suffixes into a number
>> + * @ptr: Where parse begins
>> + * @retptr: (output) Optional pointer to next char after parse completes
>> + *
>> + * Parses a string into a number. The number stored at @ptr is
>> + * potentially suffixed with K, M, G, T, P, E.
>> + */
>> +static unsigned long long _memparse(const char *ptr, char **retptr)
>> +{
>> + char *endptr; /* local pointer to end of parsed string */
>> +
>> + unsigned long long ret = simple_strtoull(ptr, &endptr, 0);
>> +
>> + switch (*endptr) {
>> + case 'E':
>> + case 'e':
>> + ret <<= 10;
>> + case 'P':
>> + case 'p':
>> + ret <<= 10;
>> + case 'T':
>> + case 't':
>> + ret <<= 10;
>> + case 'G':
>> + case 'g':
>> + ret <<= 10;
>> + case 'M':
>> + case 'm':
>> + ret <<= 10;
>> + case 'K':
>> + case 'k':
>> + ret <<= 10;
>> + endptr++;
>> + default:
>> + break;
>> + }
>> +
>> + if (retptr)
>> + *retptr = endptr;
>> +
>> + return ret;
>> +}
>> +
>> +static int
>> +parse_memmap(char *p, unsigned long long *start, unsigned long long *size)
>> +{
>> + char *oldp;
>> +
>> + if (!p)
>> + return -EINVAL;
>> +
>> + /* we don't care about this option here */
>> + if (!strncmp(p, "exactmap", 8))
>> + return -EINVAL;
>> +
>> + oldp = p;
>> + *size = _memparse(p, &p);
>> + if (p == oldp)
>> + return -EINVAL;
>> +
>> + switch (*p) {
>> + case '@':
>> + /* skip this region, usable */
>> + *start = 0;
>> + *size = 0;
>> + return 0;
> How about direclty return if nn@ss? Seems no need to waste one mem avoid
> region slot. In fact even amount of usable memory regions are provided to
> 100, it won't impact that you want to specify a reserve memmap region if
> you skip it direclty. Personal opinion.

We are not wasting the slot. If you look mem_avoid_memmap() where I call
the function, it will skip with a continue if size == 0 without
incrementing the 'i' counter. That will skip all the nn@ss regions
without counting against the max avoid mapping.

>> + case '#':
>> + case '$':
>> + case '!':
>> + *start = _memparse(p + 1, &p);
>> + return 0;
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> +static int mem_avoid_memmap(void)
>> +{
>> + char arg[128];
>> + int rc = 0;
>> +
>> + /* see if we have any memmap areas */
>> + if (cmdline_find_option("memmap", arg, sizeof(arg)) > 0) {
>> + int i = 0;
>> + char *str = arg;
>> +
>> + while (str && (i < MAX_MEMMAP_REGIONS)) {
>> + unsigned long long start, size;
>> + char *k = strchr(str, ',');
>> +
>> + if (k)
>> + *k++ = 0;
>> +
>> + rc = parse_memmap(str, &start, &size);
>> + if (rc < 0)
>> + break;
>> + str = k;
>> + /* a usable region that should not be skipped */
>> + if (size == 0)
>> + continue;
>> +
>> + mem_avoid[MEM_AVOID_MEMMAP1 + i].start = start;
>> + mem_avoid[MEM_AVOID_MEMMAP1 + i].size = size;
>> + i++;
>> + }
>> +
>> + /* more than 4 memmaps, fail kaslr */
>> + if ((i >= MAX_MEMMAP_REGIONS) && str)
>> + rc = -EINVAL;
>> + }
>> +
>> + return rc;
>> +}
>> +
>> /*
>> * In theory, KASLR can put the kernel anywhere in the range of [16M, 64T).
>> * The mem_avoid array is used to store the ranges that need to be avoided
>> @@ -429,6 +552,7 @@ void choose_random_location(unsigned long input,
>> unsigned long *virt_addr)
>> {
>> unsigned long random_addr, min_addr;
>> + int rc;
>>
>> /* By default, keep output position unchanged. */
>> *virt_addr = *output;
>> @@ -438,6 +562,13 @@ void choose_random_location(unsigned long input,
>> return;
>> }
>>
>> + /* Mark the memmap regions we need to avoid */
>> + rc = mem_avoid_memmap();
>> + if (rc < 0) {
> Or write it like below, the rc is not needed. Personal coding style,
> just talk about it.
> if (mem_avoid_memmap()) {
> warn("KASLR disabled: memmap exceeds limit of 4, giving up.");
> return;
> }

Doesn't matter to me. I'll update as you suggest.

>> +
>> boot_params->hdr.loadflags |= KASLR_FLAG;
>>
>> /* Prepare to add new identity pagetables on demand. */
>> diff --git a/arch/x86/boot/string.c b/arch/x86/boot/string.c
>> index cc3bd58..0464aaa 100644
>> --- a/arch/x86/boot/string.c
>> +++ b/arch/x86/boot/string.c
>> @@ -122,6 +122,31 @@ unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int bas
>> }
>>
>> /**
>> + * simple_strtoul - convert a string to an unsigned long
>> + * @cp: The start of the string
>> + * @endp: A pointer to the end of the parsed string will be placed here
>> + * @base: The number base to use
>> + */
>> +unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base)
>> +{
>> + return simple_strtoull(cp, endp, base);
>> +}
>> +
>> +/**
>> + * simple_strtol - convert a string to a signed long
>> + * @cp: The start of the string
>> + * @endp: A pointer to the end of the parsed string will be placed here
>> + * @base: The number base to use
>> + */
>> +long simple_strtol(const char *cp, char **endp, unsigned int base)
>> +{
>> + if (*cp == '-')
>> + return -simple_strtoul(cp + 1, endp, base);
>> +
>> + return simple_strtoul(cp, endp, base);
>> +}
>> +
>> +/**
>> * strlen - Find the length of a string
>> * @s: The string to be sized
>> */
>> @@ -155,3 +180,16 @@ char *strstr(const char *s1, const char *s2)
>> }
>> return NULL;
>> }
>> +
>> +/**
>> + * strchr - Find the first occurrence of the character c in the string s.
>> + * @s: the string to be searched
>> + * @c: the character to search for
>> + */
>> +char *strchr(const char *s, int c)
>> +{
>> + while (*s != (char)c)
>> + if (*s++ == '\0')
>> + return NULL;
>> + return (char *)s;
>> +}
>>