Skip to content
  • Michal Hocko's avatar
    mm: introduce MAP_FIXED_NOREPLACE · a4ff8e86
    Michal Hocko authored
    Patch series "mm: introduce MAP_FIXED_NOREPLACE", v2.
    
    This has started as a follow up discussion [3][4] resulting in the
    runtime failure caused by hardening patch [5] which removes MAP_FIXED
    from the elf loader because MAP_FIXED is inherently dangerous as it
    might silently clobber an existing underlying mapping (e.g.  stack).
    The reason for the failure is that some architectures enforce an
    alignment for the given address hint without MAP_FIXED used (e.g.  for
    shared or file backed mappings).
    
    One way around this would be excluding those archs which do alignment
    tricks from the hardening [6].  The patch is really trivial but it has
    been objected, rightfully so, that this screams for a more generic
    solution.  We basically want a non-destructive MAP_FIXED.
    
    The first patch introduced MAP_FIXED_NOREPLACE which enforces the given
    address but unlike MAP_FIXED it fails with EEXIST if the given range
    conflicts with an existing one.  The flag is introduced as a completely
    new one rather than a MAP_FIXED extension because of the backward
    compatibility.  We really want a never-clobber semantic even on older
    kernels which do not recognize the flag.  Unfortunately mmap sucks
    wrt flags evaluation because we do not EINVAL on unknown flags.  On
    those kernels we would simply use the traditional hint based semantic so
    the caller can still get a different address (which sucks) but at least
    not silently corrupt an existing mapping.  I do not see a good way
    around that.  Except we won't export expose the new semantic to the
    userspace at all.
    
    It seems there are users who would like to have something like that.
    Jemalloc has been mentioned by Michael Ellerman [7]
    
    Florian Weimer has mentioned the following:
    : glibc ld.so currently maps DSOs without hints.  This means that the kernel
    : will map right next to each other, and the offsets between them a completely
    : predictable.  We would like to change that and supply a random address in a
    : window of the address space.  If there is a conflict, we do not want the
    : kernel to pick a non-random address. Instead, we would try again with a
    : random address.
    
    John Hubbard has mentioned CUDA example
    : a) Searches /proc/<pid>/maps for a "suitable" region of available
    : VA space.  "Suitable" generally means it has to have a base address
    : within a certain limited range (a particular device model might
    : have odd limitations, for example), it has to be large enough, and
    : alignment has to be large enough (again, various devices may have
    : constraints that lead us to do this).
    :
    : This is of course subject to races with other threads in the process.
    :
    : Let's say it finds a region starting at va.
    :
    : b) Next it does:
    :     p = mmap(va, ...)
    :
    : *without* setting MAP_FIXED, of course (so va is just a hint), to
    : attempt to safely reserve that region. If p != va, then in most cases,
    : this is a failure (almost certainly due to another thread getting a
    : mapping from that region before we did), and so this layer now has to
    : call munmap(), before returning a "failure: retry" to upper layers.
    :
    :     IMPROVEMENT: --> if instead, we could call this:
    :
    :             p = mmap(va, ... MAP_FIXED_NOREPLACE ...)
    :
    :         , then we could skip the munmap() call upon failure. This
    :         is a small thing, but it is useful here. (Thanks to Piotr
    :         Jaroszynski and Mark Hairgrove for helping me get that detail
    :         exactly right, btw.)
    :
    : c) After that, CUDA suballocates from p, via:
    :
    :      q = mmap(sub_region_start, ... MAP_FIXED ...)
    :
    : Interestingly enough, "freeing" is also done via MAP_FIXED, and
    : setting PROT_NONE to the subregion. Anyway, I just included (c) for
    : general interest.
    
    Atomic address range probing in the multithreaded programs in general
    sounds like an interesting thing to me.
    
    The second patch simply replaces MAP_FIXED use in elf loader by
    MAP_FIXED_NOREPLACE.  I believe other places which rely on MAP_FIXED
    should follow.  Actually real MAP_FIXED usages should be docummented
    properly and they should be more of an exception.
    
    [1] http://lkml.kernel.org/r/20171116101900.13621-1-mhocko@kernel.org
    [2] http://lkml.kernel.org/r/20171129144219.22867-1-mhocko@kernel.org
    [3] http://lkml.kernel.org/r/20171107162217.382cd754@canb.auug.org.au
    [4] http://lkml.kernel.org/r/1510048229.12079.7.camel@abdul.in.ibm.com
    [5] http://lkml.kernel.org/r/20171023082608.6167-1-mhocko@kernel.org
    [6] http://lkml.kernel.org/r/20171113094203.aofz2e7kueitk55y@dhcp22.suse.cz
    [7] http://lkml.kernel.org/r/87efp1w7vy.fsf@concordia.ellerman.id.au
    
    This patch (of 2):
    
    MAP_FIXED is used quite often to enforce mapping at the particular range.
    The main problem of this flag is, however, that it is inherently dangerous
    because it unmaps existing mappings covered by the requested range.  This
    can cause silent memory corruptions.  Some of them even with serious
    security implications.  While the current semantic might be really
    desiderable in many cases there are others which would want to enforce the
    given range but rather see a failure than a silent memory corruption on a
    clashing range.  Please note that there is no guarantee that a given range
    is obeyed by the mmap even when it is free - e.g.  arch specific code is
    allowed to apply an alignment.
    
    Introduce a new MAP_FIXED_NOREPLACE flag for mmap to achieve this
    behavior.  It has the same semantic as MAP_FIXED wrt.  the given address
    request with a single exception that it fails with EEXIST if the requested
    address is already covered by an existing mapping.  We still do rely on
    get_unmaped_area to handle all the arch specific MAP_FIXED treatment and
    check for a conflicting vma after it returns.
    
    The flag is introduced as a completely new one rather than a MAP_FIXED
    extension because of the backward compatibility.  We really want a
    never-clobber semantic even on older kernels which do not recognize the
    flag.  Unfortunately mmap sucks wrt.  flags evaluation because we do not
    EINVAL on unknown flags.  On those kernels we would simply use the
    traditional hint based semantic so the caller can still get a different
    address (which sucks) but at least not silently corrupt an existing
    mapping.  I do not see a good way around that.
    
    [mpe@ellerman.id.au: fix whitespace]
    [fail on clashing range with EEXIST as per Florian Weimer]
    [set MAP_FIXED before round_hint_to_min as per Khalid Aziz]
    Link: http://lkml.kernel.org/r/20171213092550.2774-2-mhocko@kernel.org
    
    
    Reviewed-by: default avatarKhalid Aziz <khalid.aziz@oracle.com>
    Signed-off-by: default avatarMichal Hocko <mhocko@suse.com>
    Acked-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
    Cc: Khalid Aziz <khalid.aziz@oracle.com>
    Cc: Russell King - ARM Linux <linux@armlinux.org.uk>
    Cc: Andrea Arcangeli <aarcange@redhat.com>
    Cc: Florian Weimer <fweimer@redhat.com>
    Cc: John Hubbard <jhubbard@nvidia.com>
    Cc: Matthew Wilcox <willy@infradead.org>
    Cc: Abdul Haleem <abdhalee@linux.vnet.ibm.com>
    Cc: Joel Stanley <joel@jms.id.au>
    Cc: Kees Cook <keescook@chromium.org>
    Cc: Michal Hocko <mhocko@suse.com>
    Cc: Jason Evans <jasone@google.com>
    Cc: David Goldblatt <davidtgoldblatt@gmail.com>
    Cc: Edward Tomasz Napierała <trasz@FreeBSD.org>
    Cc: Anshuman Khandual <khandual@linux.vnet.ibm.com>
    Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
    Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
    a4ff8e86