Hacker News new | past | comments | ask | show | jobs | submit login
Hardening C/C++ Programs – Executable-Space Protection and ASLR (productive-cpp.com)
120 points by mrich on Dec 26, 2017 | hide | past | favorite | 29 comments



The executable space protection seems like a more general basline pattern: An entity should either be able to write or modify something, or execute it. If it can do both, that's a possible security issue. This holds true for memory pages, files on a file system and with some stretches even to firewalls and security groups.


Such an ultimatum would falls afoul of modern JIT compilation, however.


Well, it falls afoul of compilers. JIT compilation does it in memory, but a regular compiler breaks the same rules on disk - it writes a file that is executable.

But even if we were writing binaries by hand, the editor that wrote them would still be breaking the same rules. There's no getting around it. The stuff that creates executables has to break those rules.

You just want a very limited set of things that are able to do that. Even in JIT, the JIT portion of the runtime should be able to write executable pages of memory, and the rest should not be able to.

But how do you enforce that? Aye, there's the rub...


> Well, it falls afoul of compilers. JIT compilation does it in memory, but a regular compiler breaks the same rules on disk - it writes a file that is executable.

Now you got me thinking. That's avoidable on linux - you can use SELinux to disallow the compiler process to execute it's own output. You might be able to ensure that a process is able to execute the compiler xor it's output, but I'm not sure about that. I know the first one because I've spent a weekend writing an SELinux module for our application servers, which don't need to execute anything after a certain startup procedure. But let me tell you, java + SELinux is not for the faint of heart, because the executed binary is shared.

> But even if we were writing binaries by hand, the editor that wrote them would still be breaking the same rules. There's no getting around it. The stuff that creates executables has to break those rules. > You just want a very limited set of things that are able to do that.

I call that trust management for myself. For example, from a systems perspective, my configuration management is a large security issue. It downloads binaries either via a package management or via HTTPs, validates them and runs them. It has to download and run them, because that's the definition of 'provisioning a server'. And it doesn't matter if the binaries are pre-baked via docker / packer, or if chef/puppet install them in VMs on the fly, the entity 'config management' just grows larger.

As such, we have to trust our configuration management, because we have to trust something, because we can't enforce security at this point.


You can do this with an out of process JIT. Basically the JIT process has the code mapped as read/write and maps it into the runtime process as read/execute. Then you can use OS capabilities to prevent the runtime process from creating its own executable pages (on Windows you can do this by enabling ACG).


An out-of-process JIT would be too slow for optimizations like inline caches.


I implemented an out-of-process JIT for Chakra (JavaScript), and we make heavy use of inline caches.

Why do you think inline caches would be an issue?


I was thinking of the latency of updating the caches. Presumedly you just batch the updates?


...and things like UPX packers.


UPX is not affected. It sets the appropriate permissions on its pages.

This is the difference between legit and non legit. A legit software can set the permissions. A remote RCE exploit may be able to insert a payload in memory but it won't be able to change permissions.


Alternate title: "It's Probably Fine: Continuing to Ship Unsafe Programs by Pretending ROP Doesn't Exist".


Could you be a bit more specific? As can be seen from the article, this is one part of a series describing several best practices you should follow when shipping binaries. This one article does not claim to cover everything. Also check part 1 and the upcoming ones.

Also, if there is a defense against ROP and other exploits that makes it possible to do away with ASLR and ESP please let me know.


> Also, if there is a defense against ROP and other exploits that makes it possible to do away with ASLR and ESP please let me know.

Memory safety.

Everything else (ASLR, DEP, CFI, heap hardening, etc.) is just fiddling around the edges -- it's not an engineering approach that produces reliable or safe software.


Agreed; but until we get there, we must make sure the software we ship from existing codebases is as safe/unexploitable as possible.


ROP has problems with ALSR on 64 bit machine.

Security is not a binary on off thing. The harder you make it for the attacker, the better.

(however ALSR makes it difficult to deal with core dumps, ouch)


ASLR often makes it a requirement to get data disclosure (heap layout or at least GOP) before a full break.

A funny thing is that a fully static LTO processed and inlined app is extremely hard to crack once ASLR is in play since you get no returns and get to mess only with what is inside. (Unless you find some dlopen, exec or system, perhaps mmap with unsafely handled flags.) Add good NX and read only pages and it becomes a real puzzle.

And no, it does not make dealing with debugging info equipped dumps any harder, as long as walking the stack is all you need.


> A funny thing is that a fully static LTO processed and inlined app is extremely hard to crack once ASLR is in play since you get no returns and get to mess only with what is inside.

How often does that actually happen? On macOS and Windows the answer should be "never" since the syscall ABI is not stable...


On both of these platforms, ABIs and APIs are stable until you get to drawing. Which means you should probably use real process separation for the GUI - it has to be linked dynamically.


No part of the Windows syscall ABI is stable on Windows. Not win32k, not core NTAPI. You need core NTAPI to send IPC messages.


It is stable in the terms of "actually didn't change in a way to break apps" not "guaranteed to be kept compatible".

Last time they did a major break there is in Windows 2000. I recommend reading up on how LPC works.

There have been extensions though. Such as very useful pico processes in Windows 10... Major extensions in Vista too, related to security and async operation.

I recommend j00ru's blog as a good starting point. And of course Windows Internals books.


> On macOS and Windows the answer should be "never" since the syscall ABI is not stable...

Dear Golang...


    #define WINAPI __stdcall  
The windows API ABI is stable and unchanged for decades.


That has absolutely nothing to do with Windows syscall ABI.


The Windows API is. The NT syscall ABI is not.


> And no, it does not make dealing with debugging info equipped dumps any harder, as long as walking the stack is all you need.

I didn't quite get that: the core dump is just a dump of (some of) the memory mappings of the dumped process. in my experience gdb often does not manage to make sense of it (with ALSR). (one replacement strategy is to suspend a faulting process with SIGSTOP so that it is possible to attach to it later on)


For debug equipped binaries, gdb will easily spot code addresses, after walking some stack. The trouble is, if the stack is damaged it ends up being worthless.


CET is coming out soon, and I'm hopeful that this will make ROP much harder.


Hadn't heard about that one before, here are more details:

https://software.intel.com/en-us/blogs/2016/06/09/intel-rele...


Needs much better editing!




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: