Friday, June 30, 2023

Re: Immutable Page Protections

Theo de Raadt <deraadt@openbsd.org> wrote:

> > It's a cheap defense in depth protection that simplifies my use
> > case.

> But I don't see a real security benefit of what you are trying to do.

There may not be. At this point, it's more idiomatic. Don't need code?
Don't keep it. It's an experimental feature. I'm not too broken up if I
can't use it anymore. I'll find other ways, and I can always just patch
the kernel.

I arrived at this feature because it's similar to a technique in
firmware, where code access can be trivially narrowed with an MPU. The
threat there is easier to perceive, since often there isn't a well
defined kernel in which access to hardware or cryptography can be
encapsulated with better guarded entry points.

> Because then programs need active code to do so. Where will those calls
> be? Today, the kernel does it automatically before the program runs.
> You want to do it at c runtime startup? It would be the same. Or you
> want programs to have to do it themselves? Like, lock_my_text_segment()
> called from inside main? That really doesn't make sense.

I think that the immutable flag feature is useful, and I think that
making it automatic is a great security mitigation. I'm just curious
whether a feature like drop_this_init_code_I_will_never_call_again()
would also be useful.

Pledge and unveil can limit the impact of system calls, but within the
program itself, attackers can find unintended ways of using code. One
salient example I can think of where this may be dangerous is in a more
dynamic language where eval() or the code generator is needed at
startup, but is no longer needed after the program enters steady state.
We've seen plenty of attacks, like Log4J, where sloppy features can be
used to exploit a naked eval(). Certainly, one solution is to limit what
libraries are being used. But, another is to go a la carte as with
pledge, and say, "I no longer need eval(), so drop it."

Yes, dynamic languages are an entirely other can of worms, and they
often have other ways to drop code. But, I think an argument could be
made for this being useful in a static C/C++ program. Init code and
other run-once code is still callable, which means that it's ripe for
gadgets. ASLR and other mitigations make this harder, but not impossible
as we've seen with various attacks to bypass these mitigations. It's
much harder to exploit what no longer exists...

> I'll think about it a bit. But I am very much not convinced that those
> text segments you have lying around are a real risk.

That's the point of the thread. It's just food for thought. I know that
features that OpenBSD develop often pop up in other operating systems.
Dropping code, at least to me, seems like a natural progression of
dropping access to system calls.

- Justin

On Fri, Jun 30, 2023 at 8:58 AM Theo de Raadt <deraadt@openbsd.org> wrote:
>
> Justin Handville <nanolith@gmail.com> wrote:
>
> > I'm assuming that misc@ is probably the best place for this e-mail,
> > although it gets a bit in the tech@ weeds. I upgraded to 7.3 not so
> > long ago, and I noticed that a daemon I had written was no longer
> > working properly. For reasons that are probably too much to get into
> > here, I statically link the daemon. It's a single binary that makes use
> > of pledge / unveil, and privilege separation. This all works fine. It
> > also has another trick, which unfortunately no longer works in 7.3.
> >
> > To reduce the code footprint of this daemon as well as the potential
> > gadget attack surface, I have it drop any code that it will no longer
> > execute. This happens after fork / exec on a child, and also after
> > initialization code executes before the child process enters its steady
> > state. This is trivially done by grouping functions into custom page
> > aligned sections in the ELF binary, and running mprotect on these
> > sections with PROT_NONE. I considered munmap as well as other tricks,
> > but so far, this seems to be the most portable way to handle this trick
> > that I could think of between BSD and Linux. I'm sure others are more
> > clever. It's a cheap defense in depth protection that simplifies my use
> > case.
> >
> > As of OpenBSD 7.3, when the immutable flag entered mainstream, this
> > trick no longer works. Given that my trick is a total hack, I'm not too
> > broken up about it. Of course, this change led me to doing some poking
> > around.
>
> Sorry.
>
> But I don't see a real security benefit of what you are trying to do.
>
> > I noticed that in sys/uvm/uvm_map.c, an exception was granted to allow
> > Chrome to drop the write flag for a region for userland compatibility.
> > That makes sense as a temporary measure. I'm wondering, however, if it
> > might not make sense to think about this functionality differently.
> > Instead of immutable memory regions, why can't we consider a more
> > pledge-like ratcheting for memory regions, where bits can be removed,
> > but never added back? How does this impact the gadget attack surface
> > that led to the immutable flag being considered to begin with?
>
> Because then programs need active code to do so. Where will those calls
> be? Today, the kernel does it automatically before the program runs.
> You want to do it at c runtime startup? It would be the same. Or you
> want programs to have to do it themselves? Like, lock_my_text_segment()
> called from inside main? That really doesn't make sense.
>
>
> > For the time being, I extended the exception in uvm_map.c on my own
> > OpenBSD systems to allow immutable regions to be stripped of all
> > protection flags with a call to mprotect. So, in addition to allowing RW
> > to R, if the region is any combination of PROT_READ, PROT_WRITE, or
> > PROT_EXEC, then it can be reduced to PROT_NONE. This seemed the safer
> > option for patching for now.
>
> The ratchet-down piece in uvm will eventually be deleted, when we return
> to review the entire corpus of software behaviour. We left that small
> piece of behaviour due to a v8/chrome requirement, and were a bit
> fearfull that other software also had such requirements, but were short
> on doing a complete ecosystem review at the time. So if anything, this
> is going to become more strict, not less strict.
>
> > Of course, this further breaks the
> > definition of "immutable", but at least immutable regions can only have
> > protection bits removed.
>
> To me, it is unclear if permission lowering lacks a bad failure mode.
> There are a scary number of programs with sigsegv handlers, trying to
> cope with unexpected behaviours in "clever ways". If you demote permissions,
> you can get into that fragile rarely tested code, so I'm going to claim
> permission reduction might not be safe.
>
> > My reason for mailing misc@ is just to bring up this data point from a
> > single user. I'm certain that the OpenBSD developers have reasons for
> > preferring a pure immutable flag, but having a mechanism for ratcheting
> > down protections is useful at least for me, and is apparently useful
> > enough in userland going from RW to R, that an exception was carved out
> > for now. Of course, I'm more than happy to work with the developers to
> > come up with a plan for upstreaming this feature if it's something
> > useful. If not, I have no problem adding it to my personal list of
> > patches I maintain that I doubt anyone else would want or need.
>
> I'll think about it a bit. But I am very much not convinced that those
> text segments you have lying around are a real risk.

No comments:

Post a Comment