top | item 18442578

There’s a Hole in the Bottom of the C: Effectiveness of Allocation Protection [pdf]

122 points| ingve | 7 years ago |web.mit.edu | reply

88 comments

order
[+] Animats|7 years ago|reply
Short version: yet another hokey scheme for fixing buffer overruns in C fails for

   typedef struct {
        char buf[124];
        void* fptr;
    } mystruct;
because if you overrun "buf", you can overstore "fptr" without going outside the bounds of "struct". Exploit for Ngnix found.

We've got to get more code out of C and into something with subscript checking. None of the kludges for fixing C work.

[+] derf_|7 years ago|reply
One (non-generalizable) solution is to avoid using writable function pointers, to help maintain control flow integrity.

For example, in libopus we have platform-specific SIMD optimizations that are chosen at runtime.

The common solution is just to have a function pointer that points to the accelerated routine. But there are only a limited number of choices for each routine. So we put pointers to them in a static const array, which gets stored on a read-only page. Then instead of storing a pointer, we just store an index into this array. This index is chosen based on the available instruction sets detected at runtime. This lets us use the same index for every accelerated function.

Then, we pad the array size out to a power of two. To call an accelerated function, we mask off the upper bits of the index. Even if the index gets corrupted, we still call one of the functions in the array. So there's only so much damage it can do.

Obviously this doesn't apply if the set of functions you need to call is open-ended (e.g., a callback, like nginx was using). But it seems like a good pattern to follow when it applies.

[+] nneonneo|7 years ago|reply
I don’t think they actually found an exploit for Nginx: they state that “we assume such a memory bug exists”, rather than saying outright what bug they exploited. It almost sounds like they planted a bug and then exploited it in a binary that was hardened (using this non-standard hardening technique, i.e. not exploiting a binary that was ever used in a real deployment of Nginx).
[+] mirekrusin|7 years ago|reply
I always thought this is example of basic buffer overflow in C - what's different with this example? If somebody asked me to write buffer overflow example in C I'd write something that looks exactly like this, this must have been known for years, what am I missing here?
[+] kevin_thibedeau|7 years ago|reply
It would be nice if the betterC mode of Dlang would get traction. That fixes the worst weaknesses of C without having to create a whole new ecosystem.
[+] lvs|7 years ago|reply
No, it's more than that. While that struct might be a generic overrun, the question is how you check bounds on filling buf. The paper is discussing that schemes to constrain buffer size using a global hardware or software address-based size constraints ("low-fat") as bounds checks are less safe than claimed.
[+] kragen|7 years ago|reply
I don't know why they didn't put this in the abstract.
[+] pjmlp|7 years ago|reply
That will never happen with UNIX based OSes around, so what we really need, as much as I would like to nuke C, is to have developers accept some variant of "Safe C".

Solaris has it thanks to SPARC tagged memory.

Problem is when would we ever get it as mainstream, always enabled, in other platforms.

[+] dataking|7 years ago|reply
> We've got to get more code out of C and into something with subscript checking. None of the kludges for fixing C work.

shameless plug: https://www.c2rust.com

[+] ElBarto|7 years ago|reply
In such cases you can insert a barrier with a magic value to detect any array overflow.

The other thing to do is to enforce checks on array indices.

[+] ndesaulniers|7 years ago|reply
> None of the kludges for fixing C work.

Turn on ASAN in production. HWASAN may make that more palatable.

[+] rawoke083600|7 years ago|reply
Honest question: Can't you just swap around the order of fptr and buf ?
[+] writepub|7 years ago|reply
or you could move all pointers to the beginning of the struct, and could even make the pointer a const (read only).

typedef struct {

        const void* fptr; 

        char buf[124];

    } mystruct;
[+] amelius|7 years ago|reply
Wouldn't Valgrind catch this one?
[+] WalterBright|7 years ago|reply
Here's my proposal for fixing buffer overflows in C:

https://www.digitalmars.com/articles/b44.html

It's simple and it works, we've got 15 years experience with this in D.

[+] pcwalton|7 years ago|reply
I mean, sure, fat pointers are the "right" solution, but the point of "low-fat" pointers in the original paper [1] is for greater compatibility with existing C code, including at the ABI level. Adding fat pointers to C (which is what your proposal is) is incompatible with those goals. Page 2 of the paper discusses this.

[1]: https://www.comp.nus.edu.sg/~gregory/papers/ndss17stack.pdf

[+] davidgay|7 years ago|reply
I still think that the Deputy's project (from UC Berkeley) approach (https://barnowl.org/research/pubs/07-hotos-linux.pdf) is the most practical for adding bounds checks to existing C code. Basically annotate functions and data structures with the, usually available, bounds information, e.g.:

  f(int *a, int n) -> f(int *count(n) a, int n);
or

  struct { int len; int *values; }
->

  struct { int len; int *count(len) values; }
And other annotations for unions, etc.

This actually works quite well in practice - the cited paper involved us applying these annotations to a complete, bootable linux kernel. You do have to be willing to tolerate limited code changes and a few "trust me" casts

Disclaimer: I worked on the overall project, though not significantly on the Deputy part.

[+] chubot|7 years ago|reply
I agree with the framing here, and I agree a small bit of syntax would go a long way.

FWIW, you will see a class called StringPiece copied all over a lot of open source Google projects, including Chrome, Ninja, RE2, probably Android, etc.

That is essentially a pointer-length pair. I agree it would be nice to have a dedicated syntax for it, and not a class that everybody has to copy around.

C++ 17 names this string_view (long overdue!)

I just Googled and apparently there was a problem with array_view ? That's a shame.

https://www.reddit.com/r/cpp/comments/5ya5pe/whatever_happen...

[+] saagarjha|7 years ago|reply
I mean, C++ kind of solves this issue with templates:

  template<size_t N>
  void foo(int (&array)[N]) {}
[+] krferriter|7 years ago|reply
How does this fix it? It automatically includes a size_t with all array types but does it also rely on the programmer setting the size_t value correctly? If so there will be the same problems. It seems like this is exactly the same as just passing a size argument along with all array-type arguments, which is already common practice.
[+] baybal2|7 years ago|reply
I wish somebody could bring Walter into C standard committee
[+] xvilka|7 years ago|reply
Just simply slowly substitute C code, piece by piece into Rust. Building userspace less depending from C ecosystem.
[+] carlmr|7 years ago|reply
Yes, at least as long as you're working on a target supported by the Rust compiler.
[+] rurban|7 years ago|reply
> In this paper, we have illustrated a new type of attack that can bypass bounds checking spatial memory safety techniques that protect allocations.

No they have not. Bounds checking only outer structs doesnt do bounds checks on inner buffers. They haven't analyzed traditional bounds checks in C: asan, fortify, c11 Annex K, Intel MPX, only a broken application, which does none of these, and uses a wrong bounds check.

[+] nneonneo|7 years ago|reply
tl;dr: the authors overflow from one struct field into another struct field, thus avoiding any allocation protection scheme that protects individual heap allocations from each other. Most of the paper is just spent implementing basic ROP-style gadget hopping.

I’m not actually clear on what’s new or novel here. Allocation protection mechanisms exist to prevent one allocated block from overflowing into another allocated block. They usually don’t protect allocated blocks from themselves.

[+] bnastic|7 years ago|reply
Hard to read a paper that repeats “legacy languages” many times just on the first page.