A runcible nonce

In the recent panic surrounding the Heartbleed bug, we ask ourselves why, and how, these bugs still happen. We know that it was a preventable bug, with a simple fix, but with potentially important repercussions.

heartbleed

The bug is explained in non-technical (but accurate) terms here, and the patch is shown here. But that’s not what I want to talk you about. Let’s discuss the source of the problem.

The basic source of the problem, before the bug, is, of course, over-engineering. Why would one in his right mind allow such an operation? Well, we can defend the idea of the heartbeat, basically a keep-alive message, but certainly not how it was implement.

You will object that ICMP ping does that and allows variable payloads. True. But the variable payload of ping has the purpose of helping us determine whether or not the network can transport bits correctly, and if not, estimate the bit error rate. Ping, also, suffered from a similar bug.

But what is the use of having a variable length payload in the SSL heartbeat? To use as a nonce? If so, I think that a 128 or 256 bits random value can serve as a nonce, as good as any human-readable string. Fixed-sized messages do eliminate the problem of checking bounds. It also bounds compute-time. While probably negligibly, a bigger packet takes more time than a small packet to be processed (especially if copies are involved).

Long story short, someone was too smart for his own good.

*
* *

This being said, what can we do to help prevent these buffer overflow (read or write) errors? We can suggest the usual (code review, better unit tests, etc., etc.) but we could also consider techniques based on support from the operating system, run-time library or even the CPU itself.

The operating system can help us prevent some of this by using address space layout randomization, that is, giving the program and its various segments different positions in memory, so that over-reading (-writing) at a specific address is now unlikely to yield information. Taken to its extreme, the idea asks for even the code itself to be position-independent and remixed at load time. Function order in memory varies. Stack location varies. Randomization makes the task of leading a useful attack a lot harder.

The run-time library can help us secure data by allocating and freeing memory blocks as if they’re dangerous. Add padding, check if the program has over-read or over-written in the pad. Zero memory before freeing it. On Linux, for example, a new process gets zeroed-out memory, but if the program allocates and frees memory, eventually an allocated block of memory will contain some of the program’s previous data. This is solved by zeroing out the memory when we free it—something that’s not done because it is considered expensive. These techniques are usually considered “debug tools” (Electric Fence, Dmalloc).

The characteristics of the underlying machine can be exploited to create secure memory blocks. Protected mode allows for any number of segments, each with its descriptor, to be created. This allows one to specify a memory region of pre-determined size with specific permissions. If you try to read or write beyond the limits of the segment, or if you try to write to a segment that is read-only, or execute code in a segment that disallows it, the CPU itself generates an exception and the operation is prevented from completing. The exception can be trapped and dealt with in some application-specific fashion.

*
* *

new-ssl-oprah

4 Responses to A runcible nonce

  1. Cyan says:

    Could it be that there is a language problem, in allowing to manipulate pointers in the first place ?

    Imagine that the language only allows to create some “table of byte-short-long”, well whatever. Each table has a fixed maximum size (essentially, the one use to malloc).

    Then, accessing any cell into the table, by design, requires to specify the position in the table, and therefore allows a comparison of this value with the maximum range of the table.
    This would trap any “out of range” access.

    I understand that it also introduces quite some limitations, and can in some circumstances impact speed, but it also looks a lot safer.

    Or is it ? Is there any way to make the scheme crack ?

    • Why would that inherently be a problem?

      The thing is, different languages make different assumptions and have different design goals. Some have the goal of hiding all implementation details in the hope of making some types of programs easier to write, but at the same time make programming of protocols and operating systems very difficult… if not impossible.

      Others will let you manipulate the underlying machine with great freedom, showing every detail: pointers, fixed-width integer arithmetic, memory layout of members in a struct/class, etc., and these languages will make zero effort to second-guess you, because extra code to do this means less performance. This will ask you, certainly, greater care in the writing of programs. They make certain types of program more difficult than necessary to write, but make the writing of high-performance and low-level software possible (but not necessarily much easier to write).

      TL;DR: I dont think of this as “pointers are good” vs “pointers are bad let’s abstract everything”. It’s like comparing a hammer and a screwdriver and arguing about which is the safest. They’re simply not designed for the same thing. If I have a program to write, I might write it in Python, or I might write it in C++, depending on my goals, the quantity of effort I’m willing to put in it, and the nature of the task.

      (But this being said, you’re right in saying that writing a program in a lower-level language is often harder. However, I don’t think we can whisk away the inherent complexity of programming. Non-trivial programs will remain non-trivial to write.)

      • Cyan says:

        L’objectif n’est pas de parler d’un “langage qui peut tout faire”, mais seulement d’un “langage plus sûr”.

        Personnellement, je préfère développer en C, le langage de Linux,
        mais il est quand même frappant de constater
        que la plupart des failles graves de sécurité en informatique
        s’accompagnent d’un bug de type “buffer overflow”.
        Et Heartbleed en est un exemple frappant.

        Plutôt que de prétendre résoudre tous les problèmes en un seul coup, on pourrait au moins s’attaquer au problème le plus courant, et le plus grave. A l’heure où le réseau informatique est un terrain de jeux pour des cyber-criminels financés et armés par des états, il semble que la sécurité informatique prenne de l’importance face à d’autres soucis, genre optimisation des performances.

        Mais évidemment, il n’y a pas de “meilleur langage pour tout”. J’attire juste l’attention sur le fait qu’Open SSL est, par nature, une application dédiée sécurité, et qu’elle est semble-t-il dans l’obligation d’utiliser un langage hautement volatile pour son développement. Je suis étonné que cela soit à peine évoqué, ou au mieux considéré comme une nuisance sans issue.

        Alors que peut se trouver là une des pistes d’amélioration.

        • Je crois que l’aspect « contrôle et performance » dominera encore dans ce type d’applications et que C a la vie dure. Peut-être à cause de mythes urbains qui perdurent, n’empêche que de construire une entête de paquet ICMP avec C#, bonjour la migraine.

          Et au restant, je demeure sceptique: si un langage qui prend en charge les tableaux nous aiderait sûrement à éviter des erreurs dans les programmes plus banals, cette prise en charge compliquerait probablement l’écriture de programmes comme SSL où il faut construire des structures avec une disposition précise en mémoire. Prenons l’exemple de C# qui nous présente une bonne abstraction de tableaux. C’est très bien tant que nous restons dans l’abstraction présentée par le langage, mais dès que nous devons avoir accès à la structure interne, on se farcit une cascade rococo de Encoding.Truc.GetMachin, de ToByteArray, de copies, etc., ce qui alourdit la programmation, sans nous garantir que nous ne créerons pas de bugs en passant de la représentation bas niveau à la représentation haut niveau et vice-versa.

          Si un langage peut nous aider à éviter les erreurs de types buffer overflow (tout en étant utile), il reste à inventer.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: