Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Mon, 26 Sep 2016 14:08:22 -0400
From: Rich Felker <dalias@...c.org>
To: "LeMay, Michael" <michael.lemay@...el.com>
Cc: "musl@...ts.openwall.com" <musl@...ts.openwall.com>
Subject: Re: [RFC] Support for segmentation-hardened SafeStack

On Mon, Sep 26, 2016 at 10:28:40AM -0700, LeMay, Michael wrote:
> 
> 
> On 9/22/2016 16:42, Rich Felker wrote:
> >On Thu, Sep 22, 2016 at 11:00:45PM +0000, LeMay, Michael wrote:
> >>Hi,
> >>
> >>I submitted several patches to LLVM and Clang to harden SafeStack
> >>using segmentation on x86-32 [1]. See [2] for general background on
> >>SafeStack. On Linux, I have been testing my compiler changes with a
> >>modified version of musl. I currently plan to submit my musl patches
> >>if and when the prerequisite LLVM and Clang patches are accepted.
> >>One of my LLVM patches depends on the details of my musl patches,
> >>which is the main reason that I am sending this RFC now.
> >My understanding is that this is a different, incompatible ABI for
> >i386, i.e. code that uses safestack is not calling-compatible with
> >code that doesn't, and vice versa. Is that true? This is probably the
> >most significant determining factor in how we treat it.
> That's a good question, but I don't know how to answer it directly.
> 
> A salient requirement is that all code that runs while restricted
> segment limits are in effect for DS and ES must use appropriate
> segment override prefixes.  This is to direct safe stack accesses to
> SS and thus avoid violating the segment limits on DS and ES.  It is
> still possible to call code that does not satisfy that requirement
> (I will refer to such functions as "standard functions", in contrast
> to "segmentation-aware functions") in the same program, but the
> segment registers would need to be reverted to contain flat segment
> descriptors before calling such code.  Otherwise, a segment limit
> violation would occur if a standard function attempted to access
> data on the stack using DS or ES.  Of course, reverting to flat
> segment descriptors would leave the safe stacks unprotected.

In short, non-safestack code called from safestack code would try to
store data on the safe stack, which doesn't/can't work.

This could probably be avoided by a different approach that avoids
call/ret and instead uses manual pushes/pops to the safe stack and
jumps in place of call/ret. Then %ss could point to the non-safe stack
so that calling non-safestack code would work fine (but would be
unprotected as usual).

I suspect this is sufficiently costly to implement (either in
performance or implementation complexity or both) that you're not
interested in doing it that way, but I mention it for completeness.

> Another consideration is that if a standard function invokes a
> segmentation-aware function, then there is the potential for the
> standard function to pass the address of a safe stack allocation to
> the segmentation-aware function.  This is problematic, because my
> proposed compiler pass for inserting segment override prefixes
> assumes that pointers to the safe stack are not passed between
> functions.  The pass only performs intraprocedural analysis.  If an
> allocation's address is taken and passed to a subroutine, then the
> segmentation-hardened SafeStack pass will move that allocation to
> the unsafe stack.

This would also be solved by the above approach, I think.

> My proposed musl patches currently assume that all code that runs
> after restricted segment limits are enabled is segmentation-aware.

That seems reasonable, but then it has to be treated as a separate
subarch or even a separate arch.

> >>After the larger unsafe stack is allocated, I invoke the modify_ldt
> >>syscall to insert a segment descriptor with a limit that is below
> >>the beginning of the safe stacks. I load that segment descriptor
> >>into the DS and ES segment registers to block memory accesses to DS
> >>and ES from accessing the safe stacks. One purpose of my LLVM and
> >>Clang patches is to insert the necessary segment override prefixes
> >>to direct accesses to the appropriate segments.
> >The content on these stacks is purely return addresses, spills, and
> >other stuff that's only accessible to compiler-generated code, not
> >data whose addresses can be taken, right?
> If an allocation's address is taken and passed as an argument to a
> subroutine, then the segmentation-hardened SafeStack pass will move
> that allocation to the unsafe stack.
> 
> Of course, function arguments are only one of many possible
> mechanisms for passing information between functions.  For example,
> one function may attempt to store a safe stack pointer into a
> structure that gets passed to another function, or into a global
> variable.  By default, my compiler pass detects and blocks such
> memory writes.

This makes it sound like you're not implementing C but some weird
C-like language where pointers don't work. From what I can see, to
correctly implement this, "safe stack pointers" have to be an
oxymoron. Any object whose address is ever taken has to live in
addressible memory, i.e. not on the safe-stack.

> However, there are instances where such writes are
> necessary.  For example, the va_list object used to support variadic
> arguments stores a pointer to the safe stack.

Are your "safe stack pointers" just implementation-details like
va_list state? In that case I think they're valid since they can only
be accessed via va_arg. But I'm wondering why the argument list is on
the unsafe stack at all. This mandates that you copy incoming
(non-variadic) arguments rather than using them in-place, which is
very expensive for (moderately-)large aggregate-type arguments.

> I implemented other
> compiler patches to emit the SS segment override prefix when
> accessing variadic arguments, so storing the safe stack pointer into
> the va_list object should be allowed.  The intraprocedural analysis
> attempts to detect this type of write, but it currently has
> limitations on the complexity of pointer computations that it can
> handle.  Thus, I added compiler command line options to selectively
> override this analysis for certain files and allow safe stack
> pointers to be written to memory even when the compiler cannot
> verify that they are being written to va_list objects.

I don't think they should even be able to _arise_ except as
variadic-argument pointers. If they can't arise you don't need to
analyze where they're written.

> Note that manipulating safe stack addresses in registers within a
> single function is no problem.  For example, traverses_stack_p in
> expand_heap.c compares a safe stack address to other addresses.
> Actually, traverses_stack_p is interesting in other ways, since it
> assumes  that libc.auxv is on the main-thread stack.  My revised
> patches move auxv to the main-thread unsafe stack.  What checks
> should be performed in traverses_stack_p when multiple types of
> stacks are defined?

It can be omitted completely for safestack. It's just a fail-safe
workaround for buggy kernels that let you grow the brk across the
stack which presumably you don't have on x86 systems.

Rich

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.