CVE-2022-21217
An out-of-bounds write vulnerability exists in the device TestEmail functionality of reolink RLC-410W v3.0.0.136_20121102. A specially-crafted network request can lead to an out-of-bounds write. An attacker can send an HTTP request to trigger this vulnerability.
Reolink RLC-410W v3.0.0.136_20121102
RLC-410W - https://reolink.com/us/product/rlc-410w/
9.1 - CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
CWE-457 - Use of Uninitialized Variable
The Reolink RLC-410W is a WiFi security camera. The camera includes motion detection functionalities and various methods to save the recordings.
The RLC-410W offers the possibility to send motion-alert e-mail notifications. This API is only usable with admin privileges.
Is possible to use the TestEmail
API to verify that the configuration is done properly sending a test e-mail. A buffer overflow during the execution of the TestEmail
API exists due to the use of uninitialized variable. This can lead to an out of bound write vulnerability.
The TestEmail
API accepts the following JSON data format:
[
{
"cmd": "TestEmail",
"action": 0,
"param": {
"Email": {
"smtpServer": "<SERVER_NAME>",
"smtpPort": <PORT>,
"userName": "<USERNAME>",
"password": "<PASSWORD>",
"nickName": "<NICKNAME>",
"ssl": 0,
"interval": "<INTERVAL>",
"addr1": "<RECIPIENT_1>",
"addr2": "<RECIPIENT_2>",
"addr3": "<RECIPIENT_3>"
}
}
}
]
Firstly the request go through the cgiserver.cgi
’s TestEmail_API
function:
undefined4 TestEmail_API(cgi_session *session,cgi_cmd *cmd_node)
{
[...]
pcVar4 = session->cgi_obj;
test_mail_api_data = (smtp_test_mail_form_data_cgiserver *)cmd_node->paramt_data;
if (cmd_node->parsing_status == NOT_HANDLED) {
[...]
}
else {
LAB_00414728:
if (cmd_node->parsing_status == PARSE_OK) {
[...]
cgi_log(0,2,"cgi_cmd_test_email, in stage 2");
test_mail_struct.ssl = test_mail_api_data->ssl;
test_mail_struct.port = test_mail_api_data->port;
strncpy(test_mail_struct.smtp_server,test_mail_api_data->smtp_server,0x7f); [1]
strncpy(test_mail_struct.username,test_mail_api_data->username,0x7f);
strncpy(test_mail_struct.password,test_mail_api_data->password,0x7f);
strncpy(test_mail_struct.nickname,test_mail_api_data->nickname,0x7f);
strncpy(test_mail_struct.addr1,test_mail_api_data->addr1,0x7f);
strncpy(test_mail_struct.addr2,test_mail_api_data->addr2,0x7f);
strncpy(test_mail_struct.addr3,test_mail_api_data->addr3,0x7f);
iVar2 = cgi_send_msg(session,cmd_node,INTL_APP_ID,SRVM_MODULE_ID,MLTS_FUNC_ID,
&test_mail_struct,0x388,cgi_msg_callback,28000); [2]
[...]
}
[...]
}
This function prepare the provided API data into a structure. This struct is going to be received, through an IPC mechanism started at [2]
, by the device
binary. This binary is responsible to receive the provided API data and execute the TestEmail
API.
The provided data will eventually reach the smtp_send_email_wrap
function:
undefined4 smtp_send_email_wrap(void)
{
[...]
memset(rcpt_string_normal,0,0x200);
memset(rcpt_string_angular_brackets,0,0x200);
[...]
if (IPC_struct != (smtp_data_from_cgi *)0x0) {
write_msmtprc(&IPC_struct->test_email_data);
some_len = strlen(&IPC_struct->field_0x394);
if (some_len < 0x80) {
mail_content = &IPC_struct->mail_content;
mail_content_len = strlen((char *)mail_content);
if ((mail_content_len < 0x100) &&
(res_concat = concatenate_recipients
(&IPC_struct->test_email_data,
rcpt_string_normal,0x200,rcpt_string_angular_brackets,0x200), [3]
-1 < res_concat)) {
[...]
}
[...]
}
In smtp_send_email_wrap
the concatenate_recipients
function, at [3]
, is called. This function will concatenate the different recipients provided: addr1
, addr2
and addr3
, into a single string.
The concatenate_recipients
function:
undefined4
concatenate_recipients
(smtp_test_mail_form_data *testemail_data,char *recpt_normal,size_t size_1,
char *recpt_with_angular_brackets,size_t size_2)
{
[...]
uVar1 = 0xfffffa21;
if ((((testemail_data != (smtp_test_mail_form_data *)0x0) && (recpt_normal != (char *)0x0)) &&
(recpt_with_angular_brackets != (char *)0x0)) && ((0 < (int)size_1 && (0 < (int)size_2)))) {
addr_string = testemail_data->addr1;
addr_N = 0;
buff_2_ = recpt_with_angular_brackets;
buff_1_ = recpt_normal;
do {
if (*addr_string != '\0') {
addr_len = strlen(addr_string);
if (addr_N == 0) {
snprintf(buff_1_,size_1,"%s",addr_string); [4]
snprintf(buff_2_,size_2,"<%s>",addr_string);
buff_2_new_offset = addr_len + 2;
buf_1_consumed_size = -addr_len;
size_2 = (size_2 - addr_len) - 2;
buf_1_new_offset = addr_len;
}
else {
snprintf(buff_1_,size_1," %s",addr_string); [5]
snprintf(buff_2_,size_2,",<%s>",addr_string);
buf_1_new_offset = addr_len + 1;
buf_1_consumed_size = -addr_len -1;
buff_2_new_offset = addr_len + 3;
size_2 = (size_2 - addr_len) - 3;
}
buff_1_ = buff_1_ + buf_1_new_offset; [6]
size_1 = size_1 + buf_1_consumed_size; [7]
buff_2_ = buff_2_ + buff_2_new_offset;
}
addr_N = addr_N + 1;
addr_string = addr_string + 0x80;
} while (addr_N != 3);
uVar1 = 0xfffffa0f;
if ((*recpt_normal != '\0') && (uVar1 = 0xfffffa0f, *recpt_with_angular_brackets != '\0')) {
uVar1 = 0;
}
}
return uVar1;
}
This function fills, at the same time, both the recpt_normal
and recpt_with_angular_brackets
buffers. Both are stack buffers of 0x200 characters.
Let us consider only how the recpt_normal
is filled. At [4]
or [5]
is used the snprintf
function to insert one of the recipient into the buff_1_
buffer, using at most size_1
characters. The buff_1_
is a cursor of the recpt_normal
that is moved after each snprintf
at [6]
. The size_1
represents how many bytes are left into the buff_1_
buffer. This variable starts with the value equal to the size of the buffer. In this case, 0x200 as value. Then after each snprintf
the length of the recipient plus the possible space character, used at [5]
, are detracted, at [7]
, from size_1
.
Because each recipient has at most 127 characters the maximum amount of bytes, considering also the spaces used at [6]
, that can be placed in the recpt_normal
buffer is 0x180.
The concatenate_recipients
function assumes that the provided data are correctly null terminated. Because the TestEmail_API
function stores the data, that will be used by the concatenate_recipients
function, into stack buffer without initializing those, the assumption that the provided data are null terminated is not always true. This can lead to a stack based buffer overflow.
The TestEmail_API
function copies the API data using strncpy
with a size of 0x7f that is exactly 127. So, if the provided string has as size 127 or more the null terminator will not be placed.
The API data structure has the following layout:
offset size type name
0x0 0x80 char[128] smtp_server
0x80 0x4 dword ssl
0x84 0x4 dword port
0x88 0x80 char[128] username
0x108 0x80 char[128] password
0x188 0x80 char[128] nickname
0x208 0x80 char[128] addr1
0x288 0x80 char[128] addr2
0x308 0x80 char[128] addr3
The memory layout data, starting from the test_mail_struct
variable, at [1]
is:
0x7fddb900│+0x0000: 0x7fddb900
[...]
0x7fddbb08│+0x0208: 0x0053bc60 → 0x0053bbe8 → 0x0053c4d0 → 0xffffffff
[...]
0x7fddbb84│+0x0284: 0x7fddbba8 → 0x0053c548 → 0x005512b8 → 0xffffffff
0x7fddbb88│+0x0288: 0x00000000
[...]
0x7fddbc04│+0x0304: 0x76f4e4fc → <free+88> lw gp, 16(sp)
0x7fddbc08│+0x0308: 0x7711f8e4 → <pthread_mutex_unlock+0> lui gp, 0x2
[...]
0x7fddbc84│+0x0384: 0x00551348 → 0x00000000
0x7fddbc88│+0x0388: 0x7fddbd18 → 0x0053c4f8 → 0x0053c540 → 0xffffffff
The addr1
starts at offset +0x0208 and will end at offset +0x287. The last byte of the addr1
buffer is at offset +0x287 where there is, before the strncpy
, the character 7f
. If the provided addr1
string has 127 character no null terminator will appear but instead the 7f
will follow the 127 characters.
Is worth to note that, because the address 0x7fddbba8
at offset +0x0284 reference a stack address, the position at offset +0x287 will, allegedly, always contains a value similar to 7f
and anyway different than 0.
The same is true for addr2
considering the range +0x0288 to +0x307.
So if we take for instance a TestEmail
that has as addr1
and addr2
strings with 127 characters and addr3
with 100 characters, in concatenate_recipients
the second time that size_1
is updated, at [7]
, we will have:
initial_size = 512
addr1_len = (128 + 128 + 100)
addr2_len_with_space = (128 + 100 + 1)
size_1 = initial_size - addr1_len - addr2_len_with_space = -73
-73, if considered as unsigned of 4 bytes, will have a value of 4294967223. And buff_1_
will have an offset of +585 from the start of recpt_normal
. So the next snprintf
, at [5]
, will be called with a position outside the boundary of the recpt_normal
buffer with an enormous size, leading to a stack-based buffer overflow with the data of addr3
.
do_page_fault(): sending SIGSEGV to dev_main for invalid read access from 43434342
epc = 43434343
ra = 43434343
2022-01-18 - Vendor Disclosure
2022-01-19 - Vendor Patched
2022-01-26 - Public Release
Discovered by Francesco Benvenuto of Cisco Talos.