Translator's PrefaceStarting to translate this article, I assumed that the author understood the problem.
However, as some users of Habr have rightly shown (thanks to
VBart ), not everything is so simple and the mention by the author of malloc, mmap and sbrk confused him even more.
In connection with this article is of more historical interest than technical.
Update The author updated his post in the same way as the discussion in the comments to this translation.
When I wrote about a
bug in GnuTLS , I said that this is not the last serious error in the TLS stack that we will see. However, I did not expect that everything would be so pitiable.
Error in Heartbleed is a particularly nasty bug. It allows an attacker to read up to 64 KB of memory, and security researchers say:
')
Without using any confidential information or credentials, we were able to steal the secret keys used for our X.509 certificates, usernames and passwords, instant messages, email and important business documents and communication.
Bug
The fix starts here in
ssl / d1_both.c :
int dtls1_process_heartbeat(SSL *s) { unsigned char *p = &s->s3->rrec.data[0], *pl; unsigned short hbtype; unsigned int payload; unsigned int padding = 16;
So, first we get a pointer to the data in the SSLv3 entry, which looks like this:
typedef struct ssl3_record_st { int type; unsigned int length; unsigned int off; unsigned char *data; unsigned char *input; unsigned char *comp; unsigned long epoch; unsigned char seq_num[8]; } SSL3_RECORD;
The structure that describes the records contains type, length, and data. Let's
go back to
dtls1_process_heartbeat :
hbtype = *p++; n2s(p, payload); pl = p;
Translator's note: code n2s (c, s); #define n2s(c,s) ((s=(((unsigned int)(c[0]))<< 8)| \ (((unsigned int)(c[1])) )),c+=2)
The first byte of the SSLv3 entry is a type of heartbeat. The macro
n2s takes two bytes from
p and puts them in
payload . This is actually the length of the payload. Please note that the actual length in the SSLv3 entry is not checked.
The variable
pl then receives the “heartbeat” data provided by the requester.
Further, the following occurs in the function:
unsigned char *buffer, *bp; int r; buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer;
Allocated as much memory as requested by the requester: up to 65535 + 1 + 2 + 16, to be precise.
The variable
bp is the pointer used to access this memory. Then:
*bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload);
Memcpy Translator's NoteTITLE
memcpy - copies memory area
SYNTAX
#include <string.h> void *memcpy(void *dest, const void *src, size_t n);
DESCRIPTION
The memcpy () function copies n bytes from the src memory area to the dest memory area. Memory areas cannot intersect. Use memmove (3) if memory areas overlap.
RETURN VALUES
The memcpy () function returns a pointer to dest.
COMPLIANCE
SVID 3, BSD 4.3, ISO 9899
The macro
s2n does the opposite of the macro
n2s : it takes a 16-bit value and puts it in two bytes. It then sets the same requested payload length.
Translator's note: code s2n (c, s); #define s2n(s,c) ((c[0]=(unsigned char)(((s)>> 8)&0xff), \ c[1]=(unsigned char)(((s) )&0xff)),c+=2)
Then the
payload bytes from
pl , the user-supplied data are copied to the newly allocated
bp array. After that, all this is sent back to the user.
So where is the mistake?
User controls payload and pl
What if the requester does not actually send payload bytes, as it should?
What if
pl really only contains one byte?
Then memcpy will read from memory everything that was near the SSLv3 entry.
And, apparently, there are many different things nearby.
There are two ways to allocate memory dynamically using
malloc (at least in Linux): using
sbrk (2) and using
mmap (2) . If
sbrk is allocated
memory , the old heap-grows-up rules are used, which limits what can be found with it, although using several queries (especially simultaneous) you can still find some interesting things. [This section initially contained my skepticism about PoC due to the nature of how the heap works through sbrk. However, many readers have reminded me that
mmap can be used instead in
malloc , and that changes everything. Thank!]
Update from the author - this part is removed from the original article.However, if mmap is used, “Bets are made!”. Any unused memory can be allocated to mmap . This is the goal of most Heartbleed attacks.
And the most important thing: the more your requested block, the more likely it will be served by mmap , and not sbrk .
Operating systems that do not use mmap to implement malloc are likely to be slightly less vulnerable.
The location of
bp doesn’t really matter at all. The location of
pl , however, is of paramount importance. Memory for it is almost certainly allocated with sbrk () due to the mmap threshold in malloc (). However, memory for interesting materials (for example, documents or user information) is very likely to be allocated to mmap () and may be available from
pl . Several simultaneous queries will also make available some interesting data.
So what does this mean? Well, the memory allocation models for
pl dictate to us what we can read. One of the discoverers of the vulnerability said this:
Heap memory allocation models make the compromise of a private key unlikely # heartbleed # dontpanic.
- Neil Mehta (@ neelmehta) April 8, 2014
Correction
The most important part of the fix is ​​this:
if (1 + 2 + 16 > s->s3->rrec.length) return 0; hbtype = *p++; n2s(p, payload); if (1 + 2 + payload + 16 > s->s3->rrec.length) return 0; pl = p;
This code does two things: the first check stops the heartbeat of zero length.
The second if performs a check to make sure that the actual length of the record is long enough. Like this.
Lessons
What can we learn from this?
I'm a C fan. It was my first programming language, and it was the first language that I was comfortable using for professional purposes. But now I see his limitations more clearly than ever before.
After Heartbleed and the
GnuTLS bug, I think we should do three things:
- Pay for security audits of critical security infrastructure elements like OpenSSL.
- Write a lot of unit- and integration tests for these libraries.
- Start writing alternative implementations in safer languages.
Given how hard it is to write safely in C, I see no other options.