A vulnerability was recently announced that allowed an attacker to
successfully authenticate against a database without the correct
password. The issue was a call to memcpy
that resulted in a value
outside of the standard -128..127
range. To view the full
announcement you can visit
http://seclists.org/oss-sec/2012/q2/493.
Testing your install
You can do a quick test to see if you are affected. Find the location
of your mysql install and fire up gdb
.
$ gdb bin/mysql
Next, load up libmysqlclient so you can get the symbol
reference for the check_scramble
function. This is the offender in
the vulnerability.
(gdb) file lib/mysql/libmysqlclient.so
Load new symbol table from "/packages/encap/mysql-5.1.56_percona_server/lib/mysql/libmysqlclient.so"? (y or n) y
Reading symbols from /packages/encap/mysql-5.1.56_percona_server/lib/mysql/libmysqlclient.so...done.
(gdb) set logging file /home/user/mysql.out
(gdb) set logging on
Copying output to /home/user/mysql.out.
(gdb) disas check_scramble
...
Now check for the presence of memcmp
in the dump
$ cat /home/user/mysql.out | grep memcmp
If nothing is returned, you are good to go. If you dig a bit further,
you will notice that the call to memcmp
has been replaced with
repz cmpsb %es:(%rdi),%ds:(%rsi)
which will prevent for this vulnerability from occurring. If you do
see the call to memcmp
, then further analysis is required. Your
specific build of gcc may or may not expose this issue.
Detailed Analysis
You can do a simple test on your system with a short C program
#include <string.h>
#include <stdio.h>
int main() {
char *s1 = "b!r";
char *s2 = "bÿr";
int result;
result = memcmp(s1, s2, sizeof(s1));
if ((result < -128) || (result > 127)) {
fprintf(stderr, "unexpected memcmp(): %d\n", result);
return 1;
}
return 0;
}
Compile and test:
$ gcc test.c -o test
$ ./test
unexpected memcmp(): -162
Using gdb to dump this, you can see the call to memcmp:
$ gdb test
(gdb) disas main
0x0000000000400534 <+0>: push %rbp
0x0000000000400535 <+1>: mov %rsp,%rbp
0x0000000000400538 <+4>: sub $0x20,%rsp
0x000000000040053c <+8>: movq $0x40068c,-0x8(%rbp)
0x0000000000400544 <+16>: movq $0x400690,-0x10(%rbp)
0x000000000040054c <+24>: mov -0x10(%rbp),%rcx
0x0000000000400550 <+28>: mov -0x8(%rbp),%rax
0x0000000000400554 <+32>: mov $0x8,%edx
0x0000000000400559 <+37>: mov %rcx,%rsi
0x000000000040055c <+40>: mov %rax,%rdi
0x000000000040055f <+43>: callq 0x400430 <memcmp@plt>
0x0000000000400564 <+48>: mov %eax,-0x14(%rbp)
0x0000000000400567 <+51>: cmpl $0xffffff80,-0x14(%rbp)
0x000000000040056b <+55>: jl 0x400573 <main+63>
0x000000000040056d <+57>: cmpl $0x7f,-0x14(%rbp)
0x0000000000400571 <+61>: jle 0x400599 <main+101>
0x0000000000400573 <+63>: mov $0x400695,%ecx
0x0000000000400578 <+68>: mov 0x2003d9(%rip),%rax # 0x600958 <stderr@@GLIBC_2.2.5>
0x000000000040057f <+75>: mov -0x14(%rbp),%edx
0x0000000000400582 <+78>: mov %rcx,%rsi
0x0000000000400585 <+81>: mov %rax,%rdi
0x0000000000400588 <+84>: mov $0x0,%eax
0x000000000040058d <+89>: callq 0x400440 <fprintf@plt>
0x0000000000400592 <+94>: mov $0x1,%eax
0x0000000000400597 <+99>: jmp 0x40059e <main+106>
0x0000000000400599 <+101>: mov $0x0,%eax
0x000000000040059e <+106>: leaveq
0x000000000040059f <+107>: retq
If you recompile using optimizations, you will notice a slight difference:
$ gcc -O3 test.c -o test
$ ./test
<nothing else happens>
$ gdb test
(gdb) disas main
Dump of assembler code for function main:
0x00000000004004f0 <+0>: mov $0x8,%ecx
0x00000000004004f5 <+5>: sub $0x8,%rsp
0x00000000004004f9 <+9>: mov $0x40062c,%esi
0x00000000004004fe <+14>: mov $0x400630,%edi
0x0000000000400503 <+19>: repz cmpsb %es:(%rdi),%ds:(%rsi)
0x0000000000400505 <+21>: seta %dl
0x0000000000400508 <+24>: setb %al
0x000000000040050b <+27>: sub %al,%dl
0x000000000040050d <+29>: xor %eax,%eax
0x000000000040050f <+31>: movsbl %dl,%edx
0x0000000000400512 <+34>: lea 0x80(%rdx),%ecx
x0000000000400518 <+40>: cmp $0xff,%ecx
0x000000000040051e <+46>: jbe 0x400536 <main+70>
0x0000000000400520 <+48>: mov 0x2003c1(%rip),%rdi # 0x6008e8 <stderr@@GLIBC_2.2.5>
0x0000000000400527 <+55>: mov $0x400635,%esi
0x000000000040052c <+60>: callq 0x4003f0 <fprintf@plt>
0x0000000000400531 <+65>: mov $0x1,%eax
0x0000000000400536 <+70>: add $0x8,%rsp
0x000000000040053a <+74>: retq
Note that the call to memcmp
has been replaced by the repeat while
zero string operation cmpsb
. This will change the result and should
actually thwart any bad mojo that was present previously.
If you look at the code in the MySQL project in sql/password.c
, the
memcmp
only happens in one place, the check_scramble
function, which
has the following code:
my_bool
check_scramble(const char *scramble_arg,
const char *message,
const uint8 *hash_stage2)
{
SHA1_CONTEXT sha1_context;
uint8 buf[SHA1_HASH_SIZE];
uint8 hash_stage2_reassured[SHA1_HASH_SIZE];
mysql_sha1_reset(&sha1_context);
/* create key to encrypt scramble */
mysql_sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, buf);
/* encrypt scramble */
my_crypt((char *) buf, buf, (const uchar *) scramble_arg, SCRAMBLE_LENGTH);
/* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, buf, SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, hash_stage2_reassured);
return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE);
}
Note the return value is the result of the call to memcmp
. The patch
just affects that line of code. Here’s what changed:
- return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE);
+ return test(memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE));
Parting thoughts
So you can draw the natural conclusion that if you compiled MySQL with optimizations, you are most likely safe against this vulnerability. It’s always a good idea to upgrade when these kinds of patches are provided, but when the upgrade process can be daunting and possibly cause downtime, it’s nice to know that waiting for the right opportunity is a reasonable decision if you aren’t impacted.