CVE-2022-21801
A denial of service vulnerability exists in the netserver recv_command functionality of reolink RLC-410W v3.0.0.136_20121102. A specially-crafted network request can lead to a reboot. An attacker can send a malicious packet to trigger this vulnerability.
Reolink RLC-410W v3.0.0.136_20121102
RLC-410W - https://reolink.com/us/product/rlc-410w/
8.6 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H
CWE-190 - Integer Overflow or Wraparound
The reolink RLC-410W is a WiFi security camera. The camera includes motion detection functionalities and various methods to save the recordings.
A specially crafted request can lead to an integer overflow in the calculation of a buffer size. This can result in not allocating a buffer that will be dereference nevertheless. This would lead to a denial of service vulnerability due to the null pointer dereference.
The RLC-410W offers several APIs functionalities, once logged, through a binary called netserver
. The function responsible for receiving the API requests, in netserver
, is recv_command
:
undefined4 recv_command(netserver_session *session)
{
[...]
fd = bc_event_get_fd(*(undefined4 *)&session->field_0x30);
max_body_read = 10;
if (fd < 0) {
[...]
}
else {
received_data = session->received_data;
while (session->header_len <= received_data) {
session_data = session->data;
PARSE_DATA:
data_size = session_data->data_size;
if (0x9c40 < (int)data_size) { [1]
[... Invalid size ...]
}
if (0 < (int)data_size) { [2]
iVar2 = recv_body(session,fd,data_size); [3]
[...]
}
max_body_read = max_body_read + -1;
parse_recved_data(session); [4]
if (max_body_read == 0) {
return 1;
}
received_data = session->received_data;
}
iVar2 = recv_header(session,fd); [5]
if (iVar2 != -2) {
[...]
session_data = session->data;
goto PARSE_DATA;
}
[...]
}
At [5]
is called the function responsible for receiving the header data. At [1]
it is checked if the session_data->data_size
, value inside the received header, is greater or equal than 0x9c40, in this case, the parsing is aborted because of the invalid size. Otherwise, another size check is performed at [2]
, checking if session_data->data_size
is greater than 0. In this case, at [3]
, the recv_body
is called. This function will receive the remaining part of the data. Eventually, at [4]
, the parse_recved_data
is called.
The parse_recved_data
function:
uint parse_recved_data(netserver_session *session)
{
[...]
if (session->maybe_parse_state == 0) {
iVar1 = version_detect();
if (-1 < iVar1) {
return 0;
}
printf("session:%s version detect failed\n",session->client_ip);
c_client_session::state_set(session,2);
}
else if (session->maybe_parse_state == 2) {
session_data = session->data;
if (session_data->magic == (session->cmd_list).magic) {
node = (netserver_session_cmd_node *)
c_client_session::cmd_init(session,session_data->data_size + 0x18,0); [6]
if (node != (netserver_session_cmd_node *)0x0) {
__src = session->data;
node->status = NEW;
node->command_type = 0;
memcpy(node->recv_data,__src,node->recv_len);
session->received_data = session->received_data - node->recv_len;
node->cmd = session_data->cmd;
c_client_session::cmd_add(session,node); [7]
[...]
} This function, in some specific case, will be called twice for the same connection. The first time it will verify the correctness of some header data. The second time, calling the `cmd_init` function at `[6]`, a `cmd` object is created.
The cmd_init
function:
int ** c_client_session::cmd_init(netserver_session *session,uint recv_len,uint send_len)
{
[...]
puVar7 = &_mips_gp0_value;
if ((0x9c58 < recv_len) || (0x9c58 < send_len)) { [8]
printf("msg cmd send:%u or recv:%u len is too long!\n",send_len,recv_len);
return (int **)0x0;
}
[...]
if (recv_len != 0) { [9]
__s = (netserver_session_data *)net_malloc(recv_len + 1);
new_cmd_node->recv_data = __s;
if (__s == (netserver_session_data *)0x0) {
printf("func:%s line:%d msg init failed\n","cmd_init",0xc34);
goto LAB_004557c4;
}
memset(__s,0,recv_len + 1);
new_cmd_node->recv_len = recv_len;
}
[...]
}
The created cmd
object has, in this specific case, a buffer field used to copy the header and the data received. At [8]
it is checked that the provided sizes are not greater than 0x9c58. Then, at [9]
, if the recv_len
parameter differs from zero the previous mentioned buffer is allocated using the recv_len
parameter as size.
At [7]
the created cmd
is appended to the list of commands that will latter on be executed. This cmd
is latter parsed in the parse_command_list
function:
undefined4 parse_command_list(netserver_session *session)
{
[...]
cmd_node_cur = (session->cmd_list).node_base;
do {
if ((netserver_cmd_list *)cmd_node_cur == &session->cmd_list) {
return 0;
}
node_data = cmd_node_cur->node_data;
node_status = node_data->status;
if (node_status == PROGRESS) {
[...]
}
else {
if (node_status == COMPLETED) {
[...]
}
if (node_status == NEW) {
netserver_session_data = node_data->recv_data; [10]
if (netserver_session_data->cmd < 0x14e) { [11]
[...]
}
}
[...]
} while( true );
}
The buffer allocated in the branch at [9]
is, at [10]
, taken and dereferenced at [11]
accessing one field of the copied header data.
An integer overflow exists at [6]
, indeed the session_data->data_size
can be a negative integer number, this will bypass the invalid size branch at [1]
. Indeed, it is checked if the data_size
is greater than 0x9c40 as integer value, so a negative value will result in not taking that branch. If the provided data_size
is 0xffffffe8, the sum at [6]
will result in calling the cmd_init
with the recv_len
parameter equals to zero. Then the branch at [9]
will not be taken and the buffer will not be allocated, leaving zero in the recv_data
cmd
’s field. Then at [11]
this field will be dereferenced, but because it contains zero a null dereference will occur. This will result in the netserver
binary crash and eventually in the reboot of the device.
2022-01-14 - Vendor Disclosure
2022-01-19 - Vendor Patched
2022-01-26 - Public Release
Discovered by Francesco Benvenuto of Cisco Talos.