Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

x86 Sorta Secure Superset

82 views
Skip to first unread message

Mark Thorson

unread,
Apr 30, 2013, 7:58:21 PM4/30/13
to
Other posters have suggested using an ECC error encoding
as a pseudotag for invalid data and zeroing out stack
entries when popping the stack as strategies for increasing
security against malware and programming errors. I don't
see any reason these cannot be upward compatible from the
existing x86 architecture, though it might be wise to offer
a mode bit for enabling the new features because there
might be some brain-damaged code that depends, for example,
on old stack frames continuing to exist after they've been
popped.

I think the mode bit should be in CR0 or CR3, not in the
PSR or page tables because it should be a bit that is
sampled once when entering protected mode. It should not
be modifiable on-the-fly, because that would be a security
risk. If you want to run software in both legacy mode and
sorta-secure mode on the same machine at the same time,
you can use virtual machines for that.

Sorta-secure mode differs from legacy mode in these ways:

1. RET automatically invalidates the stuff popped off the
stack, including any stack space allocated for parameters
and local variables. Other instructions that pop stuff
off the stack also invalidate whatever was popped.

2. Divide by zero doesn't trap anymore. It generates
an invalid result, as indicated by a hidden tag bit in
registers or the invalid encoding in memory. Loading
an invalid memory location to a register does not trap,
it simply tags the register as invalid. Arithmetic and
logical operations using invalid operands produce an
invalid result, but it also does not trap. When the
processor registers are saved or restored, the data is
converted between the in-register and in-memory invalid
formats, so no explicit representation of the hidden tag
bits is needed on the stack.

3. What does trap are certain uses of invalid data:

a) You can't move it into the program counter,
stack pointer, or base pointer.

b) You can't access data indirectly through it,
you can't use it as an index, and you can't
jump through it. You can test a pointer to
see if it's invalid (hence a null pointer)
without trapping.

c) You can't execute it. (Very important!)

d) You can't do a conditional branch based on
the result of an operation that produces an
invalid result. A new condition code bit
indicates when the other condition bits are
invalid. The conditional branch instructions
are now 3-way branches, with the third
destination being a trap to handle the invalid
condition. You can test the invalid condition
bit, if you want to avoid the trap.

You can overwrite invalid data in registers or in memory
with valid data. What you can't do is convert invalid
data into valid data. There is no architectural way
to clear the invalid status while preserving the data.

You can overwrite valid data with invalid data, for
example by the result of division by zero. Well-written
code will do this with variables that are no longer
being used to prevent malicious data from leaking out.
When you free a block, a good memory allocator will
invalidate it.

Did I miss anything? Not trapping on divide by zero
might be a problem for virtualization. The hypervisor
would run in sorta-secure mode, and it would bounce
back any traps from legacy mode VMs that are not traps
supported by legacy mode. But legacy mode traps on
divide by zero, so maybe sorta-secure mode needs to
trap too. I don't like that, so maybe there's another
solution, like introducing a new divide instruction
that doesn't trap.

Ivan Godard

unread,
Apr 30, 2013, 7:10:25 PM4/30/13
to
We have something vaguely similar in function; that section of the
filings is currently expected to go in during July. As for what you have
missed, you need to address what happens when an invalid is passed
across a protection boundary, and how you debug programs that contain
invalids.

Ivan

MitchAlsup

unread,
Apr 30, 2013, 10:51:20 PM4/30/13
to
On Tuesday, April 30, 2013 5:54:18 PM UTC-5, Mark Thorson wrote:
> 1. RET automatically invalidates the stuff popped off the
> stack, including any stack space allocated for parameters
> and local variables.

A) you are thinking of 'RET n' not 'RET'
B) RET n is for PASCAL-like call/return interfaces--which
nobody uses these days
C) 64-bit mode does not put arguments on the stack--so there
is nothinig to pop back off
D) 64-bit mode has ADD/SUB/LEA SP to manipulate the stack
E) how many cycles are you willing to pay to NAN 1MB of memory
popped off the stack?

Mitch

Mark Thorson

unread,
May 1, 2013, 12:42:47 PM5/1/13
to
MitchAlsup wrote:
>
> E) how many cycles are you willing to pay to NAN 1MB of memory
> popped off the stack?

For a lot of applications, we're past the point
where execution bandwidth matters. Security is
more important than an increment in speed.

If there's 1MB on the stack, something wrote
that 1MB and something read it at least once.
The cost of NaNing it adds one cycle to those
two cycles, so the cost is at most 33% of time
spent on that 1MB. When you add in the time
spent doing other things while that 1MB was
on the stack, I'd guess that the overhead is
actually down in the noise.

Andy (Super) Glew

unread,
May 1, 2013, 12:32:56 PM5/1/13
to
On 4/30/2013 7:51 PM, MitchAlsup wrote:

> C) 64-bit mode does not put arguments on the stack--so there
> is nothinig to pop back off

Per http://www.agner.org/optimize/calling_conventions.pdf
64-bit can pass the first 4 int/fp/xmm parameters in regs (MS),
or 6 int / 8 fp in regs (Gnu). The rest go on the stack.

This may be enough to say that most functions do not need to pop
arguments off stack. But your absolute statement is wrong.

--
The content of this message is my personal opinion only. Although I am
an employee (currently of MIPS Technologies, which has been acquired by
Imagination Technologies; in the past of companies such as Intellectual
Ventures and QIPS, Intel, AMD, Motorola, and Gould), I reveal this only
so that the reader may account for any possible bias I may have towards
my employer's products. The statements I make here in no way represent
my employers' positions on the issue, nor am I authorized to speak on
behalf of my employers, past or present.

Ivan Godard

unread,
May 1, 2013, 1:14:11 PM5/1/13
to
On 5/1/2013 9:32 AM, Andy (Super) Glew wrote:
> On 4/30/2013 7:51 PM, MitchAlsup wrote:
>
>> C) 64-bit mode does not put arguments on the stack--so there
>> is nothinig to pop back off
>
> Per http://www.agner.org/optimize/calling_conventions.pdf
> 64-bit can pass the first 4 int/fp/xmm parameters in regs (MS),
> or 6 int / 8 fp in regs (Gnu). The rest go on the stack.
>
> This may be enough to say that most functions do not need to pop
> arguments off stack. But your absolute statement is wrong.
>

Then there are VARARGS.

Ivan

Chris Gray

unread,
May 1, 2013, 3:32:15 PM5/1/13
to
Mark Thorson <nos...@sonic.net> writes:

> 2. Divide by zero doesn't trap anymore. It generates
> an invalid result, as indicated by a hidden tag bit in
> registers or the invalid encoding in memory. Loading
> an invalid memory location to a register does not trap,
> it simply tags the register as invalid. Arithmetic and
> logical operations using invalid operands produce an
> invalid result, but it also does not trap. When the
> processor registers are saved or restored, the data is
> converted between the in-register and in-memory invalid
> formats, so no explicit representation of the hidden tag
> bits is needed on the stack.

As a programmer trying to write correct code, I want to find
out about errors as soon as possible. I want *more* traps on
invalid result, not fewer.

Having ways to mark memory as containing invalid data is
definitely a good thing. By default, I would want any fetch
of such data to trap immediately.

It's a bit like the arguments about imprecise exceptions for
floating point (wasn't that on the Alpha?). In order to know
what has happened, the programmer wants to know exactly which
operation produced the invalid result. Knowing that it was
"one of the last 6 operations" is better than nothing, but
knowing precisely is best.

--
Chris Gray

Ivan Godard

unread,
May 1, 2013, 4:08:03 PM5/1/13
to
Unfortunately mandatory exception trapping precludes speculation of any
op that can potentially trap, which includes essentially all FP ops.

Also, IEEE defines five different FP exceptions, one of which is
pervasive. I doubt that you want to trap on every occurrence of "inexact".

Ivan

Stephen Sprunk

unread,
May 1, 2013, 5:29:13 PM5/1/13
to
On 01-May-13 15:08, Ivan Godard wrote:
> On 5/1/2013 12:32 PM, Chris Gray wrote:
>> As a programmer trying to write correct code, I want to find
>> out about errors as soon as possible. I want *more* traps on
>> invalid result, not fewer.
>>
>> Having ways to mark memory as containing invalid data is
>> definitely a good thing. By default, I would want any fetch
>> of such data to trap immediately.
>
> Unfortunately mandatory exception trapping precludes speculation
> of any op that can potentially trap, ...

Not if the instruction is explicitly marked speculative, which would
never trap, or if the CPU is speculatively executing a normal
instruction, which would only trap if the exception retires.

S

--
Stephen Sprunk "God does not play dice." --Albert Einstein
CCIE #3723 "God is an inveterate gambler, and He throws the
K5SSS dice at every possible opportunity." --Stephen Hawking

Mark Thorson

unread,
May 1, 2013, 9:48:52 PM5/1/13
to
Chris Gray wrote:
>
> As a programmer trying to write correct code, I want to find
> out about errors as soon as possible. I want *more* traps on
> invalid result, not fewer.
>
> Having ways to mark memory as containing invalid data is
> definitely a good thing. By default, I would want any fetch
> of such data to trap immediately.

I think we may need a distinction between
the policy during program development and
when running the application. Sure, in
program development you want traps all the
time. Maybe that should be a separate mode.

When running the application, you need to
cope with errors, not diagnose them. For
example, you might do a whole bunch of math
and produce a result. You test the result
and if it's good you keep going. If it's
bad, maybe you can use the previous result,
or reload your inputs and run it again, or
skip this iteration and go to the next one.

But if your error can be manifested as a
trap anywhere in that bunch of math, the
trap handler doesn't know what to do.
It's outside of the main program, so it
doesn't know if any options are available
for muddling through. All it can do is
report error X at line Y and terminate.
If the application is a heart-lung machine,
the one thing you can't do is terminate.
You have to keep going, so you have to test
for and handle all errors in your program.

Chris Gray

unread,
May 2, 2013, 3:38:52 PM5/2/13
to
Mark Thorson <nos...@sonic.net> writes:

> When running the application, you need to
> cope with errors, not diagnose them. ...

> ...
> If the application is a heart-lung machine,
> the one thing you can't do is terminate.
> You have to keep going, so you have to test
> for and handle all errors in your program.

In your example, I would agree. However, I believe it depends
on the application. A compiler should abort, rather than
producing bad code, or allowing an invalid construct. An
editor or IDE should abort rather than possibly messing up
user code. Etc.


At my last job, YottaYotta (a distributed storage company
that got bought by EMC), I worked on several components
of the software running on the storage blades. Our code had
lots of asserts in it. We had a test team that ran hours
of testing, trying out all sorts of failure modes, like
cycling switch ports on and off, killing and rebooting nodes,
etc. Hours of running with racks of equipment. Most of the
tests involved full-bore I/O with data correctness testing.

One of the big reasons we did things that way is that the
very last thing a storage system should do is to silently
corrupt user data. Since our system was designed to have
backup blades at all sites, the right thing to do when
detecting an inconsistency (assert) is to reboot the blade,
and let the recovery code bring things back to normal. Many
of the problems with distributed systems like that are very
timing dependent, so on a reboot it was unlikely that the
same assert would fire. One of our common problems as
developers was to find ways to reproduce reported asserts.
And, get very good at interpreting multiple trace records!

--
Chris Gray

Robert Wessel

unread,
May 5, 2013, 1:32:41 AM5/5/13
to
On Wed, 01 May 2013 09:32:56 -0700, "Andy (Super) Glew"
<an...@SPAM.comp-arch.net> wrote:

>On 4/30/2013 7:51 PM, MitchAlsup wrote:
>
>> C) 64-bit mode does not put arguments on the stack--so there
>> is nothinig to pop back off
>
>Per http://www.agner.org/optimize/calling_conventions.pdf
>64-bit can pass the first 4 int/fp/xmm parameters in regs (MS),
>or 6 int / 8 fp in regs (Gnu). The rest go on the stack.
>
>This may be enough to say that most functions do not need to pop
>arguments off stack. But your absolute statement is wrong.


Most of the x86-64 conventions leave the "argument popping" to the
caller.
0 new messages