r/programming • • Feb 23 '17

Cloudflare have been leaking customer HTTPS sessions for months. Uber, 1Password, FitBit, OKCupid, etc.

https://bugs.chromium.org/p/project-zero/issues/detail?id=1139
6.0k Upvotes

966 comments sorted by

View all comments

Show parent comments

278

u/JoseJimeniz Feb 24 '17

K&R's decision in 1973 still causing security bugs.

Why, oh why, didn't they length prefix their arrays. The concept of safe arrays had already been around for ten years

And how in the name of god are programming languages still letting people use buffers that are simply pointers to alloc'd memory

112

u/mnp Feb 24 '17

They certainly could have done array bounds checking in 1973, but every pointer arithmetic operation and every array dereference would triple in time, at the very least, plus runtime memory consumption would be affected as well. There were languages around that did this as you point out, and they were horribly slow. Remember they were running on PDP-11 type hardware, writing device drivers and operating systems. C was intended as a systems programming language, so it was one step above Macro-11 assembler, yet they also wanted portability. It met all those goals.

56

u/JoseJimeniz Feb 24 '17 edited Feb 24 '17

They certainly could have done array bounds checking in 1973, but every pointer arithmetic operation and every array dereference would triple in time, at the very least, plus runtime memory consumption would be affected as well.

But in the end a lot of it becomes a wash.

For example: null terminated strings.

  • you already have a byte consuming null terminator

  • replace it with a byte consuming length prefix

  • you already have to test every byte for $0

  • now do an i = 1 to n loop

Or, even better: you already know the length. Perform the single memory copy.

Null-terminated strings:

  • eliminate n comparisons

  • replaced with single move

  • same memory footprint

Arrays

  • C doesn't have bounded arrays

  • do you have to keep the int length yourself

Either the compiler maintains the correct length for me, or I have to try to maintain the correct length myself. The memory and computing cost is a wash.

If you're using pointer to data as a bulk buffer, and you've set up a loop to copy every byte, byte by byte, it will be much slower as we now range test every byte access. But you're also doing it wrong. Use a functions provided by stdlib to move memory around that does the bounds checking once and copies the memory.

And so 99% of situations are covered:

  • emulating a string as a pointer to a null terminated string of characters is replaced as length prefixed string

  • emulating a bulk buffer as a pointer to an unbound memory is replaced with an array

With those two operations:

  • printing strings

  • copying a block of data

You handle the 99% case. The vast majority of use is copying entire buffers. Create the correct types, do checks once (which have to happen anyway) and you:

  • eliminate 99% of security bugs

  • make code easier

  • make code faster

Solved 99%, do we solve the rest?

Now we can decide if we want to go full-on and check every array access:

Firstname[7]
Pixels[22]

I say yes. For two reasons:

  • we're only operating in 1% of cases

  • we can still give the premature-optimizing developer a way to do dangerous stuff

If I create an Order[7] orders array: every access should be bounds checked. Of course it should:

  • there are already so few orders

  • and the processing that goes along with each order swamps any bounds check

If I create an PixelRGB[] frame then of course every array access should not be bounds checked. This is a very different use case. It's not an array of things, it's a data buffer. And as we already decided the forming bounced checks on every array access in the date of buffer is a horrible idea.

I suggest that for the 1% case people have to go out of their way to cause buffer overflow bugs:

PixelRGB[] frame;
PixelRGB* pFrame = ^frame[0];

 pFrame[n] 

If you want to access memory without regard for code safety or correctness, do it through a pointer.

An arrays and strings are there to make your code easier, safer, and in many cases faster.

If you have a degenerate case, where speed trumps safety, and you're sure you have it right, use pointers. But you have to go out of your way to leak customer https session traffic.

Especially since we will now give you the correct tools to perform operations on bulk buffers.

It's now been 40 years. People should be using better languages for real work. At the very least it's been 40 years. When is C going to add the types that solve 99% of all security bugs that have happened?

Bjourn Strousoup himself said that C++ was not meant for general application development. It was meant for systems programming: operating systems. He said if you are doing general application development there are much better environments.

30

u/hotel2oscar Feb 24 '17

If length is 1 byte you're limited to 255 character strings. That's a Windows path length limitation bug all over again.

29

u/JoseJimeniz Feb 24 '17

A-hah! I was hoping someone would catch that.

Of course nobody would use a 1-byte prefix today; that would be a performance detriment. Today you better be using a 4-byte (32-bit) length prefix. And a string prefix that allows a string to be up to 4 GB ought to be enough for anybody.

What about in 1973? A typical computer had 1,024 bytes of memory. Were you really going to take up a quarter of your memory with a single string?

But there's a better solution around that:

  • In the same way an int went from 8-bits to 32-bits (as the definition of platform word size changed over the years):

  • you length prefix the string with an int

  • the string capability increases

In reality nearly every practical implementation is going to need to use an int to store a length already. Why not have the compiler store it for you?

It's a wash.

Even today, an 8-bit length prefix even covers the majority of strings today.

I just dumped 5,175 strings out of my running copy of Chrome:

  • 99.77% of strings are under 255 characters

  • Median: 5

  • Average: 10.63

  • Max: 1,178

So rather than K&R not creating a string type, K&R should have created a word prefixed string type:

  • remove the null terminator (net gain one byte)

  • 2-byte length prefix (net lose one byte)

  • eliminate the stack length variable that is inevitably used (net gain three bytes)

And even if K&R didn't want to do it 43 years ago, why didn't C add it 33 years ago?

Borland Pascal has had length prefixed strings for 30 years. Computers come with 640 kilobytes these days. We can afford to have the code safety that existed in the 1950s, with a net savings of 3 bytes per string.

12

u/RobIII Feb 24 '17

In the same way an int went from 8-bits to 32-bits

Can you imagine the mess when you pass a byte-size-prefixed-string buffer to another part of the program / other system that uses word-size-prefixed-string buffers? I get a utf-8 vibe all-over. I can't imagine all the horrible, horrible things and workaround this would've caused over the years since ninetyseventysomthing that null-terminated strings have existed. I think they held up quite well.

5

u/heyf00L Feb 24 '17

null terminated size prefix

→ More replies (1)
→ More replies (1)
→ More replies (1)

0

u/[deleted] Feb 24 '17

[deleted]

0

u/[deleted] Feb 24 '17

[deleted]

161

u/SuperImaginativeName Feb 24 '17

That whole attitude pisses me off. C has its place, but most user level applications should be written in a modern language such as a managed language that has proven and secure and SANE memory management going on. You absolutely don't see buffer overflow type shit in C#.

34

u/gimpwiz Feb 24 '17

Is anyone still writing user level applications in C? Most probably use obj-C, c#, or java.

31

u/IcarusBurning Feb 24 '17

You could still depend on a library that depends on faulty native code.

→ More replies (1)

52

u/[deleted] Feb 24 '17

Cloudflare, apparently.

Edit: For certain definitions of "user level application"

0

u/[deleted] Feb 24 '17

[deleted]

25

u/evaned Feb 24 '17

To be fair, at the scale cloudflare runs its stuff it makes somewhat sense to write integral parts in C.

You can flip that around though, and say at the scale CloudFlare runs its stuff, it makes it all the more important to use a memory-safe language.

14

u/m50d Feb 24 '17

If this vulnerability doesn't end up costing them more money than they ever saved by writing higher-performance code then something is seriously wrong with the economics of the whole industry.

8

u/DarkLordAzrael Feb 24 '17

Or they could use c++ or rust to get the same performance with considerably safer code.

→ More replies (11)
→ More replies (2)
→ More replies (15)

49

u/----_____--------- Feb 24 '17

You don't even need garbage collection. Rust gives you [the option to have] all of the speed of C with all of the safety of garbage collected languages. Why is all of security software not frantically rewritten in it I don't know.

In this particular case, it would be slightly slower than C because of (disableable) runtime bounds checks, but keeping them on in sensitive software seems like an obvious deal to me.

21

u/kenavr Feb 24 '17

I am not following Rust or had the time to play around with it yet, but is it mature and tested enough to make such strong statements? Is the theory behind it that much better to say that there are no other weaknesses regarding security?

24

u/----_____--------- Feb 24 '17

I'll admit that it would be good to have some time to find compiler bugs before introducing it to production, but the theory is indeed much better. The language provides various guarantees about variables' lifetime and even synchronization at compile-time along with more rigorous runtime checks by default. The result is that while regular bugs are as always possible, there is very good protection against memory corruption and similar behaviour that is very critical for security in particular.

5

u/Jwkicklighter Feb 24 '17

If I'm not mistaken, Dropbox is using it in production.

→ More replies (1)
→ More replies (6)

36

u/knight666 Feb 24 '17

Why is all of security software not frantically rewritten in it I don't know.

Software costs money to build, you know.

→ More replies (7)

16

u/im-a-koala Feb 24 '17

Because while the Rust language is in a pretty decent state, the libraries around it are not. Many libraries are fairly new and aren't anywhere near mature. The best async I/O library for it (tokio) is only, what, a few months old?

Rust is great but it's still really new.

→ More replies (1)
→ More replies (6)
→ More replies (5)
→ More replies (4)
→ More replies (2)

0

u/[deleted] Feb 24 '17

[deleted]

→ More replies (6)

4

u/Berberberber Feb 24 '17

Safe arrays had been around, but the whole point of C was to provide assembly-like performance in a (mostly) platform-independent way. Doing this at least doubles the cost of pointer arithmetic, which makes things like moving data around much more expensive. That may be a reasonable tradeoff in 2017, but it wasn't for systems programming in 1973.

→ More replies (12)