2014-08-21 15:46:14 |
Jason Gerard DeRose |
bug |
|
|
added bug |
2014-08-21 15:52:49 |
Jason Gerard DeRose |
description |
In Dbase32 1.1 and earlier, both `db32dec()` and `check_db32()` are susceptible to timing attacks because of how they handle the error condition when the text to be decoded or validated contains invalid Dbase32 characters.
The problem is that both stop at the first base32 block (8 characters) containing an error. Even a fairly crude test makes it clear that you can using timing information to at least know when you've guessed another correct block.
For a somewhat contrived example, say attacker controlled input interacts with secret information in some way such that should produce valid Dbase32 text when the secret is known. Obviously this timing information is problematic then:
$ ./setup.py benchmark
Timing attack test:
1,933,352: timing_test('aAAAAAAAAAAAAAAAAAAAAAAA')
1,932,627: timing_test('AAAAAAAAaAAAAAAAAAAAAAAA')
1,903,255: timing_test('AAAAAAAAAAAAAAAAaAAAAAAA')
Worse, the 1.1 and earlier implementation probably leaks enough information to know when you've guessed another correct character, because when a block contains an error, we then step through the block again to find the first invalid character, which is used in the exception we raise. For example, consider the 1.1 check_db32() C implementation:
count = txt_len / 8;
for (block=0; block < count; block++) {
r = DB32_REVERSE[txt_buf[0]];
r |= DB32_REVERSE[txt_buf[1]];
r |= DB32_REVERSE[txt_buf[2]];
r |= DB32_REVERSE[txt_buf[3]];
r |= DB32_REVERSE[txt_buf[4]];
r |= DB32_REVERSE[txt_buf[5]];
r |= DB32_REVERSE[txt_buf[6]];
r |= DB32_REVERSE[txt_buf[7]];
if (r & 224) {
for (i=0; i < 8; i++) {
r = DB32_REVERSE[txt_buf[i]];
if (r & 224) {
PyErr_Format(PyExc_ValueError,
"invalid Dbase32 letter: %c", txt_buf[i]
);
return NULL;
}
}
PyErr_SetString(PyExc_RuntimeError, "something went very wrong");
return NULL;
}
txt_buf += 8;
}
This all came about because the original Dbase32 python reference implementation would raise an exception with the first invalid character, which seemed helpful at the time (and it did help the unit tests be a bit more rigorous).
Note that I'm not aware of any specific scenario in Dmedia in which you could actually exploit this, but this is something we should still nip in the bud regardless.
So for 1.2, I'm fixing this in the C implementation. The Python implementation has similar problems, but I'm less concerned about it because it's really just to help the correctness of the C implementation, not something I see fit for day to day use.
I think it's good for the Python implementation to be quite different, because both implementations are subject to the same unit tests and are tested against each other. |
In Dbase32 1.1 and earlier, both `db32dec()` and `check_db32()` are susceptible to timing attacks because of how they handle the error condition when the text to be decoded or validated contains invalid Dbase32 characters.
The problem is that both stop at the first base32 block (8 characters) containing an error. Even a fairly crude benchmark makes it clear that you can using timing information to at least know when you've guessed another correct block.
For a somewhat contrived example, say attacker controlled input interacts with secret information in some way such that it should produce valid Dbase32 text when the secret is known. Is this scenario, timing information can help you incrementally guess the secret, considerably reducing the effective keyspace of the secret:
$ ./setup.py benchmark
Timing attack test:
1,933,352: timing_test('aAAAAAAAAAAAAAAAAAAAAAAA')
1,932,627: timing_test('AAAAAAAAaAAAAAAAAAAAAAAA')
1,903,255: timing_test('AAAAAAAAAAAAAAAAaAAAAAAA')
Worse, the 1.1 and earlier implementation probably leaks enough information to know when you've guessed another correct character, because when a block contains an error, we then step through the block again to find the first invalid character, which is used in the exception we raise. For example, consider the 1.1 check_db32() C implementation:
count = txt_len / 8;
for (block=0; block < count; block++) {
r = DB32_REVERSE[txt_buf[0]];
r |= DB32_REVERSE[txt_buf[1]];
r |= DB32_REVERSE[txt_buf[2]];
r |= DB32_REVERSE[txt_buf[3]];
r |= DB32_REVERSE[txt_buf[4]];
r |= DB32_REVERSE[txt_buf[5]];
r |= DB32_REVERSE[txt_buf[6]];
r |= DB32_REVERSE[txt_buf[7]];
if (r & 224) {
for (i=0; i < 8; i++) {
r = DB32_REVERSE[txt_buf[i]];
if (r & 224) {
PyErr_Format(PyExc_ValueError,
"invalid Dbase32 letter: %c", txt_buf[i]
);
return NULL;
}
}
PyErr_SetString(PyExc_RuntimeError, "something went very wrong");
return NULL;
}
txt_buf += 8;
}
This all came about because the original Dbase32 python reference implementation would raise an exception with the first invalid character, which seemed helpful at the time (and it did help the unit tests be a bit more rigorous).
Note that I'm not aware of any specific scenario in Dmedia in which you could actually exploit this, but this is something we should still nip in the bud regardless.
So for 1.2, I'm fixing this in the C implementation. The Python implementation has similar problems, but I'm less concerned about it because it's really just there to help the correctness of the C implementation, not something I see fit for day to day use.
I think it's good for the Python implementation to be quite different in its approach, because both implementations are subject to the same unit tests and are tested against each other. |
|
2014-08-21 15:53:01 |
Jason Gerard DeRose |
branch linked |
|
lp:~jderose/dbase32/timing |
|
2014-08-21 16:01:02 |
Jason Gerard DeRose |
description |
In Dbase32 1.1 and earlier, both `db32dec()` and `check_db32()` are susceptible to timing attacks because of how they handle the error condition when the text to be decoded or validated contains invalid Dbase32 characters.
The problem is that both stop at the first base32 block (8 characters) containing an error. Even a fairly crude benchmark makes it clear that you can using timing information to at least know when you've guessed another correct block.
For a somewhat contrived example, say attacker controlled input interacts with secret information in some way such that it should produce valid Dbase32 text when the secret is known. Is this scenario, timing information can help you incrementally guess the secret, considerably reducing the effective keyspace of the secret:
$ ./setup.py benchmark
Timing attack test:
1,933,352: timing_test('aAAAAAAAAAAAAAAAAAAAAAAA')
1,932,627: timing_test('AAAAAAAAaAAAAAAAAAAAAAAA')
1,903,255: timing_test('AAAAAAAAAAAAAAAAaAAAAAAA')
Worse, the 1.1 and earlier implementation probably leaks enough information to know when you've guessed another correct character, because when a block contains an error, we then step through the block again to find the first invalid character, which is used in the exception we raise. For example, consider the 1.1 check_db32() C implementation:
count = txt_len / 8;
for (block=0; block < count; block++) {
r = DB32_REVERSE[txt_buf[0]];
r |= DB32_REVERSE[txt_buf[1]];
r |= DB32_REVERSE[txt_buf[2]];
r |= DB32_REVERSE[txt_buf[3]];
r |= DB32_REVERSE[txt_buf[4]];
r |= DB32_REVERSE[txt_buf[5]];
r |= DB32_REVERSE[txt_buf[6]];
r |= DB32_REVERSE[txt_buf[7]];
if (r & 224) {
for (i=0; i < 8; i++) {
r = DB32_REVERSE[txt_buf[i]];
if (r & 224) {
PyErr_Format(PyExc_ValueError,
"invalid Dbase32 letter: %c", txt_buf[i]
);
return NULL;
}
}
PyErr_SetString(PyExc_RuntimeError, "something went very wrong");
return NULL;
}
txt_buf += 8;
}
This all came about because the original Dbase32 python reference implementation would raise an exception with the first invalid character, which seemed helpful at the time (and it did help the unit tests be a bit more rigorous).
Note that I'm not aware of any specific scenario in Dmedia in which you could actually exploit this, but this is something we should still nip in the bud regardless.
So for 1.2, I'm fixing this in the C implementation. The Python implementation has similar problems, but I'm less concerned about it because it's really just there to help the correctness of the C implementation, not something I see fit for day to day use.
I think it's good for the Python implementation to be quite different in its approach, because both implementations are subject to the same unit tests and are tested against each other. |
In Dbase32 1.1 and earlier, both `db32dec()` and `check_db32()` are susceptible to timing attacks because of how they handle the error condition when the text to be decoded or validated contains invalid Dbase32 characters.
The problem is that both stop at the first base32 block (8 characters) containing an error. Even a fairly crude benchmark makes it clear that you can using timing information to at least know when you've guessed another correct block.
For a somewhat contrived example, say attacker controlled input interacts with secret information in some way such that it should produce valid Dbase32 text when the secret is known. In this scenario, timing information can help you incrementally guess the secret, considerably reducing the effective keyspace of the secret:
$ ./setup.py benchmark
Timing attack test:
1,933,352: timing_test('aAAAAAAAAAAAAAAAAAAAAAAA')
1,932,627: timing_test('AAAAAAAAaAAAAAAAAAAAAAAA')
1,903,255: timing_test('AAAAAAAAAAAAAAAAaAAAAAAA')
Worse, the 1.1 and earlier implementation probably leaks enough information to know when you've guessed another correct character, because when a block contains an error, we then step through the block again to find the first invalid character, which is used in the exception we raise. For example, consider the 1.1 check_db32() C implementation:
count = txt_len / 8;
for (block=0; block < count; block++) {
r = DB32_REVERSE[txt_buf[0]];
r |= DB32_REVERSE[txt_buf[1]];
r |= DB32_REVERSE[txt_buf[2]];
r |= DB32_REVERSE[txt_buf[3]];
r |= DB32_REVERSE[txt_buf[4]];
r |= DB32_REVERSE[txt_buf[5]];
r |= DB32_REVERSE[txt_buf[6]];
r |= DB32_REVERSE[txt_buf[7]];
if (r & 224) {
for (i=0; i < 8; i++) {
r = DB32_REVERSE[txt_buf[i]];
if (r & 224) {
PyErr_Format(PyExc_ValueError,
"invalid Dbase32 letter: %c", txt_buf[i]
);
return NULL;
}
}
PyErr_SetString(PyExc_RuntimeError, "something went very wrong");
return NULL;
}
txt_buf += 8;
}
This all came about because the original Dbase32 python reference implementation would raise an exception with the first invalid character, which seemed helpful at the time (and it did help the unit tests be a bit more rigorous).
Note that I'm not aware of any specific scenario in Dmedia in which you could actually exploit this, but this is something we should still nip in the bud regardless.
So for 1.2, I'm fixing this in the C implementation. The Python implementation has similar problems, but I'm less concerned about it because it's really just there to help the correctness of the C implementation, not something I see fit for day to day use.
I think it's good for the Python implementation to be quite different in its approach, because both implementations are subject to the same unit tests and are tested against each other. |
|
2014-08-21 16:39:00 |
Jason Gerard DeRose |
summary |
Mitagate timing leakage in error handling |
Mitigate timing leakage in error handling |
|
2014-08-23 17:12:58 |
Launchpad Janitor |
branch linked |
|
lp:dbase32 |
|
2014-08-23 17:17:38 |
Jason Gerard DeRose |
dbase32: status |
In Progress |
Fix Committed |
|
2014-08-26 17:11:01 |
Jason Gerard DeRose |
dbase32: status |
Fix Committed |
Fix Released |
|