Summary
An unprivileged local user may trigger a NULL dereference bug in Samba’s Winbind service leading to Denial of Service (DoS).
Product
Samba
Tested Version
samba-4.7.6+dfsg~ubuntu
Details
Issue 1: NULL dereference in winbindd_lookupsids_recv
Samba’s Winbind service exposes two UNIX sockets through which winbind client requests are received and answered.
One of these sockets handles unprivileged commands and the other handles privileged commands.
The unprivileged socket is available to unprivileged local users. In our Samba installation this unprivileged socket could be reached via /var/run/samba/winbindd/pipe
and does not require any special permissions to open.
anticomputer@dc1:~$ ls -alrt /var/run/samba/winbindd
total 0
drwxr-xr-x 7 root root 440 Jul 8 22:11 ..
srwxrwxrwx 1 root root 0 Jul 8 22:11 pipe
drwxr-xr-x 2 root root 60 Jul 8 22:11 .
anticomputer@dc1:~$
This presents an interesting attack surface from an attacker perspective as the Winbind service generally runs with root privileges. The available Winbind commands on the unprivileged socket are enumerated in winbindd.c:async_nonpriv_table
. Requests for these commands are supplied in the form of a user supplied (thus attacker controlled) winbind_request
data structure.
One of the available unprivileged commands is WINBINDD_LOOKUPSIDS
. This command’s incoming request handler is defined in winbindd_lookupsids.c:winbindd_lookupsids_send
as follows:
struct tevent_req *winbindd_lookupsids_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct winbindd_cli_state *cli,
struct winbindd_request *request)
{
struct tevent_req *req, *subreq;
struct winbindd_lookupsids_state *state;
req = tevent_req_create(mem_ctx, &state,
struct winbindd_lookupsids_state);
if (req == NULL) {
return NULL;
}
DEBUG(3, ("lookupsids\n"));
if (request->extra_len == 0) {
tevent_req_done(req);
[1]
return tevent_req_post(req, ev);
}
if (request->extra_data.data[request->extra_len-1] != '\0') {
DEBUG(10, ("Got invalid sids list\n"));
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
return tevent_req_post(req, ev);
}
if (!parse_sidlist(state, request->extra_data.data,
&state->sids, &state->num_sids)) {
DEBUG(10, ("parse_sidlist failed\n"));
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
return tevent_req_post(req, ev);
}
[2]
subreq = wb_lookupsids_send(state, ev, state->sids, state->num_sids);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, winbindd_lookupsids_done, req);
return req;
}
We note that this command handler normally expects to receive an extra set of data tagged onto the client request, based on the request->extra_len
variable. When this data is available, it is subsequently parsed via parse_sidlist
and the result state for the client request is updated via wb_lookupsids_send
at [2].
wb_lookupsids_send
will lookup any domains associated to the parsed sid list and subsequently update the request result state to include those domain results via the state->domains
pointer. This occurs via a call to wb_lookupsids_get_domain
which will allocate memory to store any domain results and initialize the state->domains
pointer accordingly.
However, if there is no extra data available in the request, i.e. request->extra_len
is 0, or there are no sids available in the sid list, i.e. state->num_sids
is 0, then the state->domains
pointer is not explicitly initialized.
In the case where there is extra data (i.e. request->extra_len
!= 0) but the resulting state->num_sids
remains 0 this is alleviated by the fact that wb_lookupsids_send
allocates memory into state->res_domains
, which is moved into state->domains
via a call to talloc_move
in wb_lookupsids_recv
by way of the winbindd_lookupsids_done
callback prior to any dereference of the state->domains
pointer.
However, when request->extra_len
is 0, this code path is never invoked and state->domains
remains NULL
.
The initial memory for the state structure is allocated via talloc_zero_size
, which provides memory allocations that are initialized to zero. This ensures that even when state->domains
and state->res_domains
are not properly initialized, they will always be NULL
.
After the initial request has been parsed and the request state has been updated with any pending results, result delivery for the WINBINDD_LOOKUPSIDS
is handled by winbindd_lookupsids.c:winbindd_lookupsids_recv
:
NTSTATUS winbindd_lookupsids_recv(struct tevent_req *req,
struct winbindd_response *response)
{
struct winbindd_lookupsids_state *state = tevent_req_data(
req, struct winbindd_lookupsids_state);
NTSTATUS status;
char *result;
uint32_t i;
if (tevent_req_is_nterror(req, &status)) {
DEBUG(5, ("wb_lookupsids failed: %s\n", nt_errstr(status)));
return status;
}
[1]
result = talloc_asprintf(response, "%d\n", (int)state->domains->count);
if (result == NULL) {
return NT_STATUS_NO_MEMORY;
}
for (i=0; i<state->domains->count; i++) {
fstring sid_str;
result = talloc_asprintf_append_buffer(
result, "%s %s\n",
sid_to_fstring(sid_str,
state->domains->domains[i].sid),
state->domains->domains[i].name.string);
if (result == NULL) {
return NT_STATUS_NO_MEMORY;
}
}
result = talloc_asprintf_append_buffer(
result, "%d\n", (int)state->names->count);
if (result == NULL) {
return NT_STATUS_NO_MEMORY;
}
for (i=0; i<state->names->count; i++) {
struct lsa_TranslatedName *name;
name = &state->names->names[i];
result = talloc_asprintf_append_buffer(
result, "%d %d %s\n",
(int)name->sid_index, (int)name->sid_type,
name->name.string);
if (result == NULL) {
return NT_STATUS_NO_MEMORY;
}
}
response->extra_data.data = result;
response->length += talloc_get_size(result);
return NT_STATUS_OK;
}
At [1] we then notice a dereference of state->domains
, which means that there exists an opportunity to trigger a NULL dereference, as we can craft a result state in which state->domains
remains unitialized by sending a WINBINDD_LOOKUPSIDS
command with request->extra_len
set to 0.
Doing so results in the following crash:
Program received signal SIGSEGV, Segmentation fault.
0x000055687ae13f61 in winbindd_lookupsids_recv (req=0x55687cc3ccd0,
response=0x55687d3c5470) at ../source3/winbindd/winbindd_lookupsids.c:103
103 result = talloc_asprintf(response, "%d\n", (int)state->domains->cou
nt);
(gdb) x/i$pc
=> 0x55687ae13f61 <winbindd_lookupsids_recv+177>: mov (%rax),%edx
(gdb) i r rax
rax 0x0 0
(gdb) p state->domains
$5 = (struct lsa_RefDomainList *) 0x0
(gdb) bt
#0 0x0000561efefb6f61 in winbindd_lookupsids_recv (req=0x561f00f42930, response=0x561f018977d0)
at ../source3/winbindd/winbindd_lookupsids.c:103
#1 0x0000561efef7d1dd in wb_request_done (req=0x561f00f42930)
at ../source3/winbindd/winbindd.c:755
#2 0x00007facf6e191a4 in tevent_common_loop_immediate ()
On NULL
dereference the Winbind service will segfault and its signal handling will then abort the process. This results in a Denial of Service against any functionality of the local system that depends on the Winbind service.
Impact
This issue may lead to Denial of Service.
Remediation
Ensure the state->domains
pointer is verified to be initialized prior to any use in winbindd_lookupsids_recv
.
CVE
- CVE-2020-14323
Coordinated Disclosure Timeline
- 07/09/2020: Vendor contacted
- 07/09/2020: Vendor acknowledges report receipt
- 09/30/2020: Vendor requests extension of embargo for October
- 10/29/2020: Vendor published advisory and fixed releases
Resources
- Vendor Advisory: https://www.samba.org/samba/security/CVE-2020-14323.html
Credit
This issue was discovered and reported by GHSL team member @anticomputer (Bas Alberts).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2020-134
in any communication regarding this issue.