We have identified a new heap buffer overflow vulnerability in Samsung’s baseband implementation (mainly used in Exynos chipsets). The vulnerability can be exploited to achieve arbitrary code execution in the baseband runtime.
The vulnerability we are disclosing in this advisory affected a wide range of Samsung devices, including phones on the newest Exynos chipsets. The June 2023 issue of the Samsung Mobile Security Bulletin contains this vulnerability as CVE-2023-21517.
Vulnerability Details
Layer 3 LTE NAS messages are composed of various Information Elements and the Exynos modem implementation defines dedicated parser functions for each IE type. The vulnerability is within the parser function of the Traffic Flow Template IE (3GPP TS 24.008 10.5.6.12). Each TFT is a collection of packet filters used to map traffic to a given PDP context. When a new TFT is created or an existing one is extended then the TFT IE contains a Packet Filter List with one or more Packet Filters. These Packet Filters themselves contain unspecified number of Components that define the traffic mapping rules. The Components define source and destination ports and/or IP ranges that make up the filter rules. The TFT IE is used in several Evoled Session Management (ESM) messages: it is a mandatory field in the ACTIVATE DEDICATED EPS BEARER CONTEXT REQUEST (3GPP TS 24.301 8.3.3.1) and optional field in the MODIFY EPS BEARER CONTEXT REQUEST (3GPP TS 24.301 8.3.18.1) network originated ESM messages.
When a TFT IE is encountered, the protocol implementation parses it into an internal representation.
The TFT itself is represented by a global structure (tft_desc
) which can contain pointers to up to 15 Packet Filter descriptors (pf_desc
).
The pf_desc
structures are dynamically allocated on the modem heap and they contain a 10 element array of pointers to the Components descriptors (comp_desc
) which are also allocated dynamically.
The number of Packet Filters are defined as 15 by the specification and this maximum count is correctly verified.
However, the maximum total number of Components within the Packet Filters is not explicitly defined or enforced by the implementation.
The specification actually mandates a maximum of 10 implicitly, but this is not followed by the implementation.
The components are parsed from the IE as long as the sum of their lengths remains within the IE and the Packet Filter size.
The maximum size of the TFT IE and a Packet Filter is 255 and 251 bytes respectively and a minimum size of a Component is 2 bytes.
As a result, a Packet Filter can contain more than 10 Components (up to ~125) in which case the vulnerable parser function overflows the allocated pf_desc
structure with pointers to comp_desc
structures, that can each contain up to 33 bytes of attacker controlled data.
The pseudo code of the structures are presented below:
struct tft_desc {
char tft_op : 4;
char pf_cnt : 4;
char unk0;
short unk1;
struct pf_desc* pfs[15];
void* unk2;
}
struct pf_desc {
char unk0;
char eps_bearer_id;
char pf_dir : 4;
char pf_id : 4;
char pf_eval_prec;
char pf_len;
char comp_count;
short unk1;
struct comp_desc* components[10]; // This is the overflown buffer
}
sizeof(struct pf_desc) == 48
struct comp_desc {
char comp_type;
char comp_data[32];
}
sizeof(struct comp_desc) == 33
The vulnerable function is called SAEQM_ParseToFormattedTft
based on the debug strings.
The parser function processes the Packet Filter list in case the TFT operation is “Create New TFT”, “Add Packet Filter to Existing TFT” or “Replace Packet Filter in Existing TFT”.
For each Packet Filter, = the struct pf_desc
is allocated from the heap first (see the pseudo code below at [0]).
Then, during the first loop [1] the number of Components and the total sum of their sizes is calculated based on the OTA message content.
The code verifies that the total size of Components is the same as the declared Packet Filter length and that it remains within the IE.
However the total number of Components is never checked.
The second loop [2] goes over the components again.
This time a struct comp_desc
is allocated for each of them and the pointer is then saved in the previously allocated struct pf_desc->components[]
array.
The content of the Component is copied from the OTA message to the struct comp_desc
, always 33 bytes regardless of the actual Component size.
The size of the components array is fixed at 10, however more than 100 Components can fit into a malformed Packet Filter.
The heap overflow occurs when the component pointer is saved at [3] as the index is incremented for each Component.
The index is only verified against the previously calculated component count at [4], and never against the absolute size of the buffer.
The result of the vulnerability is a modem heap overflow with pointers to buffers that contain up to 33 bytes of attacker controlled data.
int __fastcall SAEQM_ParseToFormattedTft(
unsigned int eps_bearer_id,
unsigned __int8 *IE_ptr,
unsigned int IE_size,
int cause) {
[...]
sael3_context_log("NEW_TFT"); // NEW TFT OP
IE_pf_list = (char *)(IE_ptr + 1); // Received Message Bytes
currLen = 1;
pf_idx = 0;
stored_tft_op_ = stored_tft_smh;
do
{
if ((currLen + 3) > IE_size )
{
dbg_print('Decoding Error Current Len[%d] Received Len [%d]', (unsigned __int16)(currLen_ + 3), IE_size, 0xFECDBA98);
goto EXIT;
}
new_pf = (struct pf_desc *)pal_malloc(4u, 48u, aLtesaeLtel3Lte_59, 0x137Fu); // [0] Packet Filter buffer is allocated
tft->pfs[pf_idx] = new_pf;
SAEL3_memclr(new_pf, 48u);
new_pf->pf_dir = IE_pf_list[0]>>4 & 0xf;
new_pf->pf_id = IE_pf_list[0] & 0xf;
new_pf->pf_eval_prec = IE_pf_list[1];
new_pf->pf_len = IE_pf_list[2];
IE_data = IE_pf_list + 3;
if ( new_pf->pfc_len ) // [1] First loop: Iterate over the PFC-s and verify the length
{
comp_total_len = 0;
comp_cnt = 0;
while ( 1 )
{
pf_comp_len = sael3_tft_get_pf_comp_len((unsigned __int8)*IE_data);
if ( !pf_comp_len )
break;
++comp_cnt; // Components are counted here, but not verified
comp_total_len += pf_comp_len + 1;
IE_data += pf_comp_len + 1;
if ( comp_tatal_len >= new_pf->pf_len )
break;
}
comp_cnt = 0;
}
else
{
comp_total_len = 0;
}
currLen += 3;
if ( comp_total_len != new_pf->pf_len )
{
dbg_print('Mistmatch Len & Decoding Len [%d], [%d]', new_pf->pf_len, comp_total_len, -20071784);
comp_cnt = 0;
}
new_pf->comp_count = comp_cnt;
if ( !new_pf->comp_count )
{
dbg_print('NumComponent = 0 -> SAEQM_SYNTACT_ERROR_IN_PKT_FILTER [%d]', pf_id, -20071784);
break;
}
// Opcode/pf id verification emitted for the sake of brevity
dbg_print('PacketFilter[%d] Precedence[0x%x] Dir[%d] NumCompo [%d]',
pf_id, pf_eval_prec, (pf_dir_id >> 4) & 3, new_pf->comp_count, -20071784);
IE_data = IE_pf_list + 3;
cmp_idx = 0;
v49 = 0;
while ( 1 ) // [2] Second loop: Allocate and initialise components
{
if ( !sael3_tft_get_pf_comp_len((unsigned __int8)*IE_data) )
{
dbg_print('Error :: Component Type Error !', -20071784);
goto ERROR_EXIT;
}
new_pf->components[cmp_idx] = (struct comp_desc *)pal_malloc(4u, 33u, aLtesaeLtel3Lte_59, 0x13C8u); // [3] This is where the overflow happens
SAEL3_memclr(new_pf->components[cmp_idx], 0x21u);
memcpy(new_pf->components[cmp_idx], IE_data, sizeof(struct comp_desc)); // Note that this can overread
comp_len = sael3_tft_get_pf_comp_len((unsigned __int8)*IE_data);
currLen += comp_len + 1;
IE_data += comp_len + 1;
if ( currLen_ > IE_size )
goto ERROR_EXIT;
dbg_log_comp_type(new_pf->components[cmp_idx]->comp_type);
curr_comp = new_pf->components[cmp_idx];
sael3_tft_dbg_log_pf_comp(curr_comp->comp_data, comp_len);
cmp_idx++;
if ( cmp_idx >= new_pf->comp_count ) // [4] This comes from loop 1 and can be larger than 10
{
break;
}
}
sael3_log_tft_context_end();
IE_pf_list = IE_data;
pf_idx++;
}
while ( pf_idx < pf_cnt );
When the device receives a network originated ACTIVATE DEDICATED EPS BEARER CONTEXT REQUEST radio message, the SAEL3 task first dispatches it to the general EMM handler.
The EMM stack (SAEMM) processes the security headers, decrypts and verifies the integrity of the message and places the encapsulated ESM message into the internal queue of the SAEL3 task.
The internal message dispatcher of SAEL3 pops the message from the queue and passes it to the ESM (SAEQM) stack.
The ESM dispatcher sets up and calls the message specific handler routines, called SAEQM_HdlrActDedicatedEpsBearerContextReq
in this specific example.
Each dedicated message handler has an associated parser routine that executes the general IE decoder function with the appropriate message type as the parameter.
The IE decoder processes the received radio message bytes by calling the dedicated parser function for each contained Information Element.
The IE parser receives three parameters: the EPS Bearer ID on which the radio message was received, a pointer to the content of IE (containing the decryped message bytes) and the length of the IE.
The parser for the TFT IE ends up calling the vulnerable SAEQM_ParseToFormattedTft
function, passing on the same parameters with the addition of a 4th parameter, which is the Cause value.
Affected Devices
All LTE-capable Samsung chipsets containing Samsung’s baseband implementation, including all LTE-capable Exynos chipsets.
Fix
Samsung OTA images, released after June 2023, contain the fix for the vulnerability.
Timeline
- 2022.12.04. Bug reported to Samsung PSIRT by Anonymous (based on information in Samsung 2023/06 Security Bulletin)
- 2023.04.09. Samsung releases monthly security update
- 2022.04.12. TASZK confirms the vulnerability has been patched silently
- 2023.06.11. Samsung releases security bulletin
- 2023.09.26. TASZK informs Samsung of disclosure plan, Samsung confirms
- 2023.11.03. Vulnerability released at Hardwear.io
- 2023.11.26. Advisory release