[GTALUG] A find alternative: fselect

D. Hugh Redelmeier hugh at mimosa.com
Sat Jun 15 12:02:30 EDT 2019


| From: David Mason via talk <talk at gtalug.org>

| I was unclear. I completely agree that in the C/Assembly world, 
| bug-fixes are almost always security related. In the Rust world, 
| security bugs are very hard to create (not impossible, but hard), so 
| most *Rust* bug-fixes are bugs for sure, but not security bugs.

Rust does a few things that are relevant (I think -- I've not actually 
used Rust):

- when programming language abstractions are broken (eg. array bound 
  violations), the program aborts rather than continue doing
  (exploitable) nonsense.

- unfortunately, I think that Rust only catches integer overflow in
  debug mode.  That's a mistake, but it's probably because checking is
  considered too expensive.
  <http://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/>

- it takes real work to get dangling pointers (pointers that point to 
  freed memory).  You cannot do it in Rust, except at a higher level
  (i.e. by creating your own simulation of pointers)

- it takes work to create leaks (but leaks are not usually exploitable
  except for Denial of Service)

Any of these bugs in C code can, and regularly do, lead to security
problems.  Furthermore, since they are not checked by the system,
these bugs can apparently debugged code.

But there is a lot of scope for security bugs that isn't covered by
these improvements.

Dave: is this a summary of what you are saying?

	It is often possible to exploit a C program bug to inject
	arbitrary machine code into the execution.

	Rust program bugs cannot be exploited this way.  (Assuming you
	don't use "unsafe".)

================

Anecdote: overflow as a security hole

Here's the first security bug that I remember exploiting.  This was
about 1970, in WATFIV, a Fortran compiler that, among other things,
was supposed to keep the program safely bottled up.

	character*1 m(256,256,256,256)

This ostensibly gave me an array "m" of 4GiB (the machine had 1MiB of
real memory, no virtual memory, and a number of other jobs) --
impossible.  But the compiler got an overflow doing the size
calculation (in 32 bits) and actually allocated 0 bytes.  I could then
subscript this array, passing bounds checks, and access all memory
available to the process running the program.  Havoc ensued.

(I was a Good Guy.  I told the compiler maintainer and this bug was
fixed.)

Years later I went to a talk by one of the people who had created
PL/C, the Cornell PL/1 compiler.  He claimed that their compiler was
100% safe.  I said that I thought that with an interface as wide as
the PL/1 language, that this was unlikely but the speaker was
confident (it had been used for safely running student programs for
several years in several universities).

Within about 20 minutes of the talk, I had broken it (slowing me down:
I had to use punch-cards).

- same trick

- but their compiler did check for overflows

- so I moved the array declaration to run-time (a PL/I feature)

- the runtime system caught the overflow

- but the PL/1 language allows overflow checking to be suppressed in
  your code.  When I did this, I could use this trick.

Like a combination lock, it took the interaction of several features
to get in.  That's one reason security problems are so hard to find.

Cornell subsequently fixed their compiler so that runtime overflow checks
were only suppressed in user's code, not in the internally generated
array-size calculation.

According to
<https://www.ibm.com/support/knowledgecenter/SSQ2R2_9.5.1/com.ibm.ent.pl1.zos.doc/topics/xf5640.htm>
PL/I no longer checks for overflow on FIXED BINARY operations, which
include normal integer operations.  Too bad.

================

On the IBM/360, it was possible to get an interrupt on overflow but it
isn't on the PDP-11 and its successors (eg. the x86 family, ARM,
etc.).  So it is no longer possible to get overflow checking "for
free".

That's probably why Rust and PL/I no longer check for integer overflow
as a matter of course.

With current hardware, code that calculates the correct result may be
more efficient than code that catches overflow in each possible place.
By that I mean faithfully doing the calculation in a wide-enough type
and only checking when the result must be stored in a narrower type.

If overflow is checked on each operation, +, -, and * are no longer
commutative and associative.  This confuses programmer and hamstrings
compilers.

================

Pascal lets you define variables and formal parameters as belonging to
a subrange of integers.  For a few years I programmed in a Pascal
variant that forced all integer declarations to be of this form.  I
found it very a powerful tool for capturing and enforcing these invariants
and I wish Rust allowed this.  It makes overflow much more evident.

It also gets rid of defining integer widths by modifiers such as
	"char", "short", "long", "long long"
	"signed", "unsigned"
or by the number of bits.
A subrange is a more natural way of describing the desired capactiy of
a variable.


More information about the talk mailing list