Received: by 10.223.185.116 with SMTP id b49csp5411589wrg; Tue, 27 Feb 2018 12:53:57 -0800 (PST) X-Google-Smtp-Source: AH8x226RiD5p5UaRYaPyv8p6HfRhHqj9YeIFgDsBXyiC4K9m/yQwriWGj/UnePxJNAhJ/URutdz5 X-Received: by 10.101.65.131 with SMTP id a3mr12096775pgq.270.1519764837717; Tue, 27 Feb 2018 12:53:57 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1519764837; cv=none; d=google.com; s=arc-20160816; b=dD8FS0Udv0AAEub4O/f5d7zzKtk9qVqmJs6xz9lnLVTi+tX9svi+YDgIyrjVBAYcU4 Rs1vcN9fH8zInJPUL6iYD6V/UONMd7CaMh9tD/OeBtRw/Iip2ey9duAhXp0qXhY59RoF AlYhQiFggEvUmA4usxBVq1rn+83epJ4ZVH0ETalVmoCrUdzxKOW8FgMjRdXey6a2EQ1f NVMoKvFLCTDdLZGByJE/Vk4gl/LuJQU3D9XBxRjveEEnLYcqwqi8brsHgfQtJ3ZeSxYc 2ogV2K8ERRZkXj8Skg0MgaFMyma07SMN0ZL4yqb5YCCDl8KJ2HtlNDwBYRvgBdAc8rPU sAJQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:cc:to:subject :message-id:date:from:references:in-reply-to:mime-version :dkim-signature:dkim-signature:arc-authentication-results; bh=uZg0oc49wHwWNruqxdadf5Vf4dRZUO6NqKcAuCRgRvQ=; b=QRdJ+doXxoAisxYwp7vKrDiL02Nqzm4S3ND/scLUlswX4ReC+bjbFA+klQLZcjNBtt xaR20XI8mwH6tsK8D+dZ6U1mJRrzQ53jfLXsdqxajlYSI17JpZqbmViZV+dH8FJdNzM+ pyr3sOmXtFbKWkCr2yeHcdmzjbbdAZzrAi0ExFK3dWwc9OSBlLXapDdxUeO19sEI1WMA BQ9dcfSh6vjkqthxnq/7YVHVj0ALU/cSNL26pyzkJzH/9ECVYgShmc1KxdCEBmCquee2 x3+qpGjNqgGEKT7WPth2Z+TM446su5dVPgU/ZDHmDLpdPTh+saYHrHoeHM5lW/qnHHag iBdA== ARC-Authentication-Results: i=1; mx.google.com; dkim=fail header.i=@google.com header.s=20161025 header.b=qVirbgOR; dkim=fail header.i=@chromium.org header.s=google header.b=A0HaqiYi; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=chromium.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id u6-v6si28605pls.148.2018.02.27.12.53.42; Tue, 27 Feb 2018 12:53:57 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=fail header.i=@google.com header.s=20161025 header.b=qVirbgOR; dkim=fail header.i=@chromium.org header.s=google header.b=A0HaqiYi; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=chromium.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751985AbeB0Uwf (ORCPT + 99 others); Tue, 27 Feb 2018 15:52:35 -0500 Received: from mail-vk0-f66.google.com ([209.85.213.66]:35459 "EHLO mail-vk0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751948AbeB0Uwb (ORCPT ); Tue, 27 Feb 2018 15:52:31 -0500 Received: by mail-vk0-f66.google.com with SMTP id b65so133825vka.2 for ; Tue, 27 Feb 2018 12:52:31 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=mime-version:sender:in-reply-to:references:from:date:message-id :subject:to:cc:content-transfer-encoding; bh=uZg0oc49wHwWNruqxdadf5Vf4dRZUO6NqKcAuCRgRvQ=; b=qVirbgORoVQVW6j+LiVV9ijl/5BgfVR9cC94QHxtb3Fm1w9MB7lOgcK8yqQgB6b1VM apHFbDxyP4l4JEpgKRXL6uv5hv5PYmlsmsS1vaZLZjjw0KqHYUiNo6rfL3zrrIuzpYlX s7PFp8uA6M96wOaTuCk2pZXvTPOT/QfQgqUsJBMu+e9OPCKSWYstP2qtEb+OoYCl6ct4 QaIIYnqVX/WiIzwBUV+MsSjeIRAxXc2f99zwHVbaS+l45LVpW+EX/E8pGju5BW0O/q2h cuCan1LwLFmfnzpwxMZj4yE3X2P7vlkwfoJpKTTEKd2hAklxFCQ0QXJ92wO5wJodFPbT wheQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=mime-version:sender:in-reply-to:references:from:date:message-id :subject:to:cc:content-transfer-encoding; bh=uZg0oc49wHwWNruqxdadf5Vf4dRZUO6NqKcAuCRgRvQ=; b=A0HaqiYilt9wXkrOApFXkXiAjGq29kCsOKQdHiVQL2B2NuOzJslFeaTLOmbDSrWEBX RMlC62phBm8bUABSrP4GENuSAXne19PkwZiMtG4q596I64tFsaZgDKmEAzgRKOZmsXla S196OuF1NZHVkQ7rMlgfvVZhHae6xgxDof1jQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:sender:in-reply-to:references:from :date:message-id:subject:to:cc:content-transfer-encoding; bh=uZg0oc49wHwWNruqxdadf5Vf4dRZUO6NqKcAuCRgRvQ=; b=QeliCnbIjWG2zOIO7rag3QgXkfGL7N3S7i7zXdmCmbupssB6yIymX5mpT66iH16tbr B+NwK380yk0tMzjmkqE5H3aJbw6OLAWKVQMdUapxJZNSgHnKiugpK5b4oA55/YLnodA8 N3072Qhj71AJpfNaPHvhhksDit0ZdpYB7A+AyCqOyArercXYv01sJOO/1IO4H/UquAbK 3+0lIyHeeN6d1+pVuI9nBj/k6JqaFQaCaXTJleMFVkAnSkpJdnBTGV64qygF/Ok7VZHV UJJoeREcz34tdewfaI+RdrDDwhCupCHGKlsfJpQ8niFhoCqBQqRuRX9buR1PnnxIHr6l n3tQ== X-Gm-Message-State: APf1xPCR4XT9jACY2FAmQmF2lZ5Sv0LlL1d1SJt1xfPanRdsNqz6KYVg trQxdsJFWJsnollsaf7N2Fwy8Ov5rHmw1YUmICbE2g== X-Received: by 10.31.168.142 with SMTP id r136mr11388440vke.149.1519764750226; Tue, 27 Feb 2018 12:52:30 -0800 (PST) MIME-Version: 1.0 Received: by 10.31.242.140 with HTTP; Tue, 27 Feb 2018 12:52:29 -0800 (PST) In-Reply-To: <20180227131338.3699-1-blackzert@gmail.com> References: <20180227131338.3699-1-blackzert@gmail.com> From: Kees Cook Date: Tue, 27 Feb 2018 12:52:29 -0800 X-Google-Sender-Auth: B0i2rpZ0wwoh78OgwMQEprmwjts Message-ID: Subject: Re: [RFC PATCH] Randomization of address chosen by mmap. To: Ilya Smith Cc: Andrew Morton , Dan Williams , Michal Hocko , "Kirill A. Shutemov" , Jan Kara , Jerome Glisse , Hugh Dickins , Matthew Wilcox , Helge Deller , Andrea Arcangeli , Oleg Nesterov , Linux-MM , LKML , Kernel Hardening Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Tue, Feb 27, 2018 at 5:13 AM, Ilya Smith wrote: > This is more proof of concept. Current implementation doesn't randomize > address returned by mmap. All the entropy ends with choosing mmap_base_ad= dr > at the process creation. After that mmap build very predictable layout > of address space. It allows to bypass ASLR in many cases. I'd like more details on the threat model here; if it's just a matter of .so loading order, I wonder if load order randomization would get a comparable level of uncertainty without the memory fragmentation, like: https://android-review.googlesource.com/c/platform/bionic/+/178130/2 If glibc, for example, could do this too, it would go a long way to improving things. Obviously, it's not as extreme as loading stuff all over the place, but it seems like the effect for an attack would be similar. The search _area_ remains small, but the ordering wouldn't be deterministic any more. > This patch make randomization of address on any mmap call. > It works good on 64 bit system, but usage under 32 bit systems is not > recommended. This approach uses current implementation to simplify search > of address. It would be worth spelling out the "not recommended" bit some more too: this fragments the mmap space, which has some serious issues on smaller address spaces if you get into a situation where you cannot allocate a hole large enough between the other allocations. > > Here I would like to discuss this approach. > > Signed-off-by: Ilya Smith > --- > include/linux/mm.h | 4 ++ > mm/mmap.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++= ++++++ > 2 files changed, 175 insertions(+) > > diff --git a/include/linux/mm.h b/include/linux/mm.h > index ad06d42adb1a..f81b6c8a0bc5 100644 > --- a/include/linux/mm.h > +++ b/include/linux/mm.h > @@ -25,6 +25,7 @@ > #include > #include > #include > +#include > > struct mempolicy; > struct anon_vma; > @@ -2253,6 +2254,7 @@ struct vm_unmapped_area_info { > unsigned long align_offset; > }; > > +extern unsigned long unmapped_area_random(struct vm_unmapped_area_info *= info); > extern unsigned long unmapped_area(struct vm_unmapped_area_info *info); > extern unsigned long unmapped_area_topdown(struct vm_unmapped_area_info = *info); > > @@ -2268,6 +2270,8 @@ extern unsigned long unmapped_area_topdown(struct v= m_unmapped_area_info *info); > static inline unsigned long > vm_unmapped_area(struct vm_unmapped_area_info *info) > { > + if (current->flags & PF_RANDOMIZE) > + return unmapped_area_random(info); I think this will need a larger knob -- doing this by default is likely to break stuff, I'd imagine? Bikeshedding: I'm not sure if this should be setting "3" for /proc/sys/kernel/randomize_va_space, or a separate one like /proc/sys/mm/randomize_mmap_allocation. > if (info->flags & VM_UNMAPPED_AREA_TOPDOWN) > return unmapped_area_topdown(info); > else > diff --git a/mm/mmap.c b/mm/mmap.c > index 9efdc021ad22..58110e065417 100644 > --- a/mm/mmap.c > +++ b/mm/mmap.c > @@ -45,6 +45,7 @@ > #include > #include > #include > +#include > > #include > #include > @@ -1780,6 +1781,176 @@ unsigned long mmap_region(struct file *file, unsi= gned long addr, > return error; > } > > +unsigned long unmapped_area_random(struct vm_unmapped_area_info *info) > +{ > + // first lets find right border with unmapped_area_topdown Nit: kernel comments are /* */. (It's a good idea to run patches through scripts/checkpatch.pl first.) > + struct mm_struct *mm =3D current->mm; > + struct vm_area_struct *vma; > + struct vm_area_struct *right_vma =3D 0; > + unsigned long entropy; > + unsigned int entropy_count; > + unsigned long length, low_limit, high_limit, gap_start, gap_end; > + unsigned long addr, low, high; > + > + /* Adjust search length to account for worst case alignment overh= ead */ > + length =3D info->length + info->align_mask; > + if (length < info->length) > + return -ENOMEM; > + > + /* > + * Adjust search limits by the desired length. > + * See implementation comment at top of unmapped_area(). > + */ > + gap_end =3D info->high_limit; > + if (gap_end < length) > + return -ENOMEM; > + high_limit =3D gap_end - length; > + > + info->low_limit =3D 0x10000; > + if (info->low_limit > high_limit) > + return -ENOMEM; > + low_limit =3D info->low_limit + length; > + > + /* Check highest gap, which does not precede any rbtree node */ > + gap_start =3D mm->highest_vm_end; > + if (gap_start <=3D high_limit) > + goto found; > + > + /* Check if rbtree root looks promising */ > + if (RB_EMPTY_ROOT(&mm->mm_rb)) > + return -ENOMEM; > + vma =3D rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb)= ; > + if (vma->rb_subtree_gap < length) > + return -ENOMEM; > + > + while (true) { > + /* Visit right subtree if it looks promising */ > + gap_start =3D vma->vm_prev ? vm_end_gap(vma->vm_prev) : 0= ; > + if (gap_start <=3D high_limit && vma->vm_rb.rb_right) { > + struct vm_area_struct *right =3D > + rb_entry(vma->vm_rb.rb_right, > + struct vm_area_struct, vm_rb); > + if (right->rb_subtree_gap >=3D length) { > + vma =3D right; > + continue; > + } > + } > + > +check_current_down: > + /* Check if current node has a suitable gap */ > + gap_end =3D vm_start_gap(vma); > + if (gap_end < low_limit) > + return -ENOMEM; > + if (gap_start <=3D high_limit && > + gap_end > gap_start && gap_end - gap_start >=3D lengt= h) > + goto found; > + > + /* Visit left subtree if it looks promising */ > + if (vma->vm_rb.rb_left) { > + struct vm_area_struct *left =3D > + rb_entry(vma->vm_rb.rb_left, > + struct vm_area_struct, vm_rb); > + if (left->rb_subtree_gap >=3D length) { > + vma =3D left; > + continue; > + } > + } > + > + /* Go back up the rbtree to find next candidate node */ > + while (true) { > + struct rb_node *prev =3D &vma->vm_rb; > + > + if (!rb_parent(prev)) > + return -ENOMEM; > + vma =3D rb_entry(rb_parent(prev), > + struct vm_area_struct, vm_rb); > + if (prev =3D=3D vma->vm_rb.rb_right) { > + gap_start =3D vma->vm_prev ? > + vm_end_gap(vma->vm_prev) : 0; > + goto check_current_down; > + } > + } > + } Everything from here up is identical to the existing unmapped_area_topdown(), yes? This likely needs to be refactored instead of copy/pasted, and adjust to handle both unmapped_area() and unmapped_area_topdown(). > + > +found: > + right_vma =3D vma; > + low =3D gap_start; > + high =3D gap_end - length; > + > + entropy =3D get_random_long(); > + entropy_count =3D 0; > + > + // from left node to right we check if node is fine and > + // randomly select it. > + vma =3D mm->mmap; > + while (vma !=3D right_vma) { > + /* Visit left subtree if it looks promising */ > + gap_end =3D vm_start_gap(vma); > + if (gap_end >=3D low_limit && vma->vm_rb.rb_left) { > + struct vm_area_struct *left =3D > + rb_entry(vma->vm_rb.rb_left, > + struct vm_area_struct, vm_rb); > + if (left->rb_subtree_gap >=3D length) { > + vma =3D left; > + continue; > + } > + } > + > + gap_start =3D vma->vm_prev ? vm_end_gap(vma->vm_prev) : l= ow_limit; > +check_current_up: > + /* Check if current node has a suitable gap */ > + if (gap_start > high_limit) > + break; > + if (gap_end >=3D low_limit && > + gap_end > gap_start && gap_end - gap_start >=3D lengt= h) { > + if (entropy & 1) { > + low =3D gap_start; > + high =3D gap_end - length; > + } > + entropy >>=3D 1; > + if (++entropy_count =3D=3D 64) { > + entropy =3D get_random_long(); > + entropy_count =3D 0; > + } > + } > + > + /* Visit right subtree if it looks promising */ > + if (vma->vm_rb.rb_right) { > + struct vm_area_struct *right =3D > + rb_entry(vma->vm_rb.rb_right, > + struct vm_area_struct, vm_rb); > + if (right->rb_subtree_gap >=3D length) { > + vma =3D right; > + continue; > + } > + } > + > + /* Go back up the rbtree to find next candidate node */ > + while (true) { > + struct rb_node *prev =3D &vma->vm_rb; > + > + if (!rb_parent(prev)) > + BUG(); // this should not happen > + vma =3D rb_entry(rb_parent(prev), > + struct vm_area_struct, vm_rb); > + if (prev =3D=3D vma->vm_rb.rb_left) { > + gap_start =3D vm_end_gap(vma->vm_prev); > + gap_end =3D vm_start_gap(vma); > + if (vma =3D=3D right_vma) mm/mmap.c: In function =E2=80=98unmapped_area_random=E2=80=99: mm/mmap.c:1939:8: warning: =E2=80=98vma=E2=80=99 may be used uninitialized = in this function [-Wmaybe-uninitialized] if (vma =3D=3D right_vma) ^ > + break; > + goto check_current_up; > + } > + } > + } What are the two phases here? Could this second one get collapsed into the first? > + > + if (high =3D=3D low) > + return low; > + > + addr =3D get_random_long() % ((high - low) >> PAGE_SHIFT); > + addr =3D low + (addr << PAGE_SHIFT); > + return addr; > +} > + > unsigned long unmapped_area(struct vm_unmapped_area_info *info) > { > /* How large are the gaps intended to be? Looking at the gaps on something like Xorg they differ a lot. Otherwise, looks interesting! -Kees --=20 Kees Cook Pixel Security