Date: Sat, 25 Apr 2020 17:00:55 -0000
On Fri, Apr 24, 2020 at 11:14 PM Nevin Liber via SG12
<sg12_at_[hidden]> wrote:
> More importantly, the threat this is supposed to mitigate needs to be described in much greater detail. Right now, it looks like the only threat it mitigates is one on a single core machine with a von Neumann architecture and a not too aggressive compiler, and that to me is not a solution worth standardizing.
It's not about completely preventing any threat; it's about harm reduction.
The intent is not that programs would use secure_clear and then
intentionally hand control of their address space to an untrusted
program. It's not even that programs would use secure_clear and then
do something that has a *risk* of being exploited. Typically, there's
no separation in time between those two states. Rather, the exploit
could come at any time, including while sensitive data is in the midst
of being processed, in which case it obviously hasn't been cleared.
(Even if the program is single-threaded, which of course is
increasingly rare, the threat model typically also includes the
attacker compromising the system it's running on and extracting
secrets using a debugger.) That means there is never a guarantee that
any particular piece of sensitive data will be protected.
The intent is that *if* the attacker happens to come along after some
data has already finished being processed, then hopefully they won't
be able to get at that particular data.
It's not much of a guarantee. But it only comes into play if the
enemy is already inside the gates and, chances are, in a position to
do serious damage regardless of what measures are applied. It's too
late to avoid that damage; we can only do our best to limit it.
As such, mitigations are useful even if they're not guaranteed to
work. I'm not saying we should standardize something even if it turns
out to be pure snake oil, and I'm not saying we shouldn't look for
better designs that can guarantee more. But whereas APIs usually look
for hard guarantees – e.g. "if you do X, there probably won't be a
data race, unless the compiler does something weird" would be useless
as a concurrency primitive – this is one of those rare situations
where "might work" is actually better than nothing.
That said...
>From a more concrete perspective, it would be interesting to have a
primitive like "call this function, then clear the stack memory and
registers it used". That would come closer to guaranteeing that
automatic variables and temporary objects from that function
invocation would be kept secret. It still wouldn't be a hard
guarantee, since the compiler could stash copies of them in other
locations, perhaps intentionally as temporary storage, but more
usually inadvertently – e.g. an object on the stack has uninitialized
padding bytes leak data from previously-executed code, and the object
then gets copied to the heap with padding intact. Also, such a
primitive might be too expensive for some code. In a typical userland
scenario, the stack is multiple MBs in size and clearing the whole
thing by hand would be quite expensive; a better approach is to ask
the OS how much was actually used, but that would likely require a
syscall at minimum. Still, a syscall is not too bad, and this
approach would be substantially more robust than secure_clear alone.
Here is prior art for a similar approach being used to protect the Linux kernel:
https://sudonull.com/post/10604
But note that even with such a primitive, you'd still want some kind
of securely-clearable *heap* memory.
<sg12_at_[hidden]> wrote:
> More importantly, the threat this is supposed to mitigate needs to be described in much greater detail. Right now, it looks like the only threat it mitigates is one on a single core machine with a von Neumann architecture and a not too aggressive compiler, and that to me is not a solution worth standardizing.
It's not about completely preventing any threat; it's about harm reduction.
The intent is not that programs would use secure_clear and then
intentionally hand control of their address space to an untrusted
program. It's not even that programs would use secure_clear and then
do something that has a *risk* of being exploited. Typically, there's
no separation in time between those two states. Rather, the exploit
could come at any time, including while sensitive data is in the midst
of being processed, in which case it obviously hasn't been cleared.
(Even if the program is single-threaded, which of course is
increasingly rare, the threat model typically also includes the
attacker compromising the system it's running on and extracting
secrets using a debugger.) That means there is never a guarantee that
any particular piece of sensitive data will be protected.
The intent is that *if* the attacker happens to come along after some
data has already finished being processed, then hopefully they won't
be able to get at that particular data.
It's not much of a guarantee. But it only comes into play if the
enemy is already inside the gates and, chances are, in a position to
do serious damage regardless of what measures are applied. It's too
late to avoid that damage; we can only do our best to limit it.
As such, mitigations are useful even if they're not guaranteed to
work. I'm not saying we should standardize something even if it turns
out to be pure snake oil, and I'm not saying we shouldn't look for
better designs that can guarantee more. But whereas APIs usually look
for hard guarantees – e.g. "if you do X, there probably won't be a
data race, unless the compiler does something weird" would be useless
as a concurrency primitive – this is one of those rare situations
where "might work" is actually better than nothing.
That said...
>From a more concrete perspective, it would be interesting to have a
primitive like "call this function, then clear the stack memory and
registers it used". That would come closer to guaranteeing that
automatic variables and temporary objects from that function
invocation would be kept secret. It still wouldn't be a hard
guarantee, since the compiler could stash copies of them in other
locations, perhaps intentionally as temporary storage, but more
usually inadvertently – e.g. an object on the stack has uninitialized
padding bytes leak data from previously-executed code, and the object
then gets copied to the heap with padding intact. Also, such a
primitive might be too expensive for some code. In a typical userland
scenario, the stack is multiple MBs in size and clearing the whole
thing by hand would be quite expensive; a better approach is to ask
the OS how much was actually used, but that would likely require a
syscall at minimum. Still, a syscall is not too bad, and this
approach would be substantially more robust than secure_clear alone.
Here is prior art for a similar approach being used to protect the Linux kernel:
https://sudonull.com/post/10604
But note that even with such a primitive, you'd still want some kind
of securely-clearable *heap* memory.
Received on 2020-04-25 12:00:54