We often analyze the security of devices that implement a Trusted Execution Environment (TEE) and the information described in this post is a result of that. One of the goals of a Trusted Execution Environment (TEE) is to provide an environment for the secure execution of Trusted Applications (TAs).
The source code for a TA is rarely available for attackers and often only the binary code is available. As always, there are several exceptions like the default TAs provided by OP-TEE, an open-source TEE OS, and the TAs available for the Ledger hardware wallet which is used for storing cryptocurrencies securely.
While analyzing the open-source code of Samsung TVs, made available because of GPL licenses, we identified that the published code for the J-series included the source code for a TA named tzdemuxerservice. This TA actually is licenses against LGPL. After a quick initial source code review we concluded several vulnerabilities were present as relevant security good practices were not followed.
We informed Samsung about the vulnerabilities via the Samsung Smart TV Security Bug Bounty Program. Samsung indicated to us that all the vulnerabilities described in this post are now fixed in the latest software version. To the best of our knowledge, they only applied to Samsung TVs from the J-series, (produced in 2015).
This post is published in agreement with Samsung, who has also provided confirmation that the content here reported is correct. Both Samsung and us never requested CVEs for these vulnerabilities. Therefore, no CVEs are available for the vulnerabilities described in this post.
Trusted Applications
The Samsung TV (J-series) we analyzed is build around an ARM-based System-on-Chip (SoC). This SoC supports ARM TrustZone, which provides the hardware primitives for building a TEE. A simplified overview of the software stack for a typical device implementing a TrustZone-based TEE is shown below.
An application in the untrusted Normal World is able to communicate with the TAs in the trusted Secure World. In order to do so securely, a communication channel is used that’s provided by the TEE which is implemented by hardware primitives and high privileged code like the Secure Monitor and the TEE OS. While the exact nature of this channel depends on the TEE itself, of which many components are unavailable to us, we can still identify the attack surface of the TA from the perspective of an attacker that’s in control of the untrusted Rich Execution Environment (REE) in the Normal World.
The GlobalPlatform (GP) API is often supported by TEEs. Actually, these GP API is supported by all TEEs we have analyzed. They are either supported directly, like is done in OP-TEE, or via a translation layer library. When supported by a TA, it implements the TA interface which defines standard entry points for the relevant stages of its life-cycle.
TA_CreateEntryPoint()
TA_DestroyEntryPoint()
TA_OpenSessionEntryPoint()
TA_CloseSessionEntryPoint()
TA_InvokeCommandEntryPoint()
This TA interface is described in more detail in GlobalPlatform's TEE Internal Core API Specification. From an attacker's point of view, the TA_OpenSessionEntryPoint()
and TA_InvokeCommandEntryPoint()
are typically most interesting as they implement actions that can controlled by an attacker in control of the REE or another TA.
In this post we do not intend to discuss Samsung's TEE or the general aspects of TEEs in detail. Please consider signing up for our TEEPwn training if you're interested in learning more about TEE security in an exciting hands-on exercise driven experience.
tzdemuxerservice
The entry point for the TA is easily found in the tzdemuxerservice.cpp
source file by identifying the TA_InvokeCommandEntryPoint()
function.
...
extern "C"
TEE_Result TA_InvokeCommandEntryPoint(const void *sessionContext,
uint32_t commandID, uint32_t param_types, TEE_Param param[4])
{
TA_PRINT("commandID %d\n",commandID);
...
This entry point implements multiple commands which are implemented using a switch
statement where each command is implemented using a dedicated case
statement.
...
switch(commandID)
{
case CMD_TZDEMUXER_CLIENT_INIT:
...
break;
case CMD_TZDEMUXER_CLIENT_INIT_WITH_PRESET_INFO:
...
break;
case CMD_TZDEMUXER_CLIENT_SUBMIT_NORMAL_DATA:
...
break;
case CMD_TZDEMUXER_CLIENT_SUBMIT_TS_DATA:
...
break;
case CMD_TZDEMUXER_CLIENT_DUMP_NORMAL_DATA:
...
break;
case CMD_TZDEMUXER_CLIENT_DUMP_VIDEO_PACKET:
...
break;
case CMD_TZDEMUXER_CLIENT_DROP_PACKET:
...
break;
case CMD_TZDEMUXER_CLIENT_GET_PACKET:
...
break;
case CMD_TZDEMUXER_CLIENT_GET_VIDEO_CODEC_INFO:
...
break;
case CMD_TZDEMUXER_CLIENT_GET_AUDIO_CODEC_INFO:
...
break;
case CMD_TZDEMUXER_CLIENT_DEINIT:
...
break;
We analyzed the entire TA and identified multiple vulnerabilities which can be exploited by an attacker in control of the REE or another TA.
Vulnerabilities
Most of the vulnerabilities identified in this post are applicable as the developers do not check the parameter types of the parameters passed by the REE
. This is fundamentally important for assuring the security of a TA as is outlined in Section 4.3.6.1 of GlobalPlatform's TEE Internal Core API Specification.
The type for param[0]
is not checked and therefore param[0]
can be passed to the TA as any type (i.e. value or reference). This allows providing a memory reference as a value which means the TEE OS does not verify if the parameters (i.e. buf
and size
) point to valid REE memory (i.e. don’t overlap with TEE memory). Effectively this means the passed parameters can point to any arbitrary address, including REE memory and TEE memory.
It must be noted that vulnerability 1 to 5 could also be considered a single vulnerability as the core problem is the param[0]
parameter is not checked. However, we decided to also touch upon the different ways the unchecked parameter can be used and therefore listed the paths as seperate vulnerabilities.
Vulnerability 1: restricted write to arbitrary address
As buf
can point to any arbitrary address, it's possible to write 32768
of buffer.data
to an arbitrary location with the TA memory, which can result in a crash of the TA or code execution in the context of the TA.
File: tzdemuxerservice.cpp
...
929 ! buf = (char *)param[0].memref.buffer;
930 ! size = param[0].memref.size;
...
1103 case CMD_TZDEMUXER_CLIENT_DUMP_NORMAL_DATA:
1104 {
1105 buffer_s buffer;
1106 buffer_list_h pipelist = &sctx->pipe_list;
1107 int ret = buffer_list_take(pipelist,&buffer,32768);
1108 if(ret != BUFFER_LIST_SUCCESS)
1109 {
1110 result = TEE_ERROR_GENERIC;
1111 break;
1112 }
1113 ! memcpy(buf,buffer.data,32768);
...
Samsung’s fix: Samsung indicated to us that the param[0]
parameter is now
verified to be TEE_PARAM_TYPE_MEMREF_INOUT
which is likely an adequate fix for this vulnerability as the TEE OS is now able to check if the passed buffer is entirely contained in REE memory.
Vulnerability 2: restricted write to arbitrary address
As buf
can point to any arbitrary address, it's possible to write pkt.size
of pkt.data
to an arbitrary location with the TA memory, which can result in a crash of the TA or code execution in the context of the TA.
File: tzdemuxerservice.cpp
...
929 ! buf = (char *)param[0].memref.buffer;
930 ! size = param[0].memref.size;
...
...
1118 case CMD_TZDEMUXER_CLIENT_DUMP_VIDEO_PACKET:
1119 {
1120 TA_PRINT("CMD_TZDEMUXER_CLIENT_DUMP_VIDEO_PACKET\n");
1121 AVPacket pkt;
1122 memset(&pkt,0x0,sizeof(AVPacket));
1123 int r = packet_queue_get(&sctx->pkt_queue,&pkt,PACKET_QUEUE_NON_BLOCK_MODE);
1124 if(r != PACKET_QUEUE_SUCCESS)
1125 {
1126 result = TEE_ERROR_GENERIC;
1127 TA_PRINT("ERROR in\n");
1128 if(r == PACKET_QUEUE_NO_PACKET)
1129 {
1130 TA_PRINT("TEE_ERROR_NO_DATA in\n");
1131 result = TEE_ERROR_NO_DATA;
1132 }
1133 break;
1134 }
1135 if(sctx->streams[pkt.stream_index] == CODEC_TYPE_VIDEO)
1136 {
1137 TA_PRINT(" Dump Video r %d, pkt.size %d\n",r,pkt.size);
1138 ! memcpy(buf,pkt.data,pkt.size);
...
Samsung’s fix: Samsung indicated to us that the param[0]
parameter is now
verified to be TEE_PARAM_TYPE_MEMREF_INOUT
which is likely an adequate fix for this vulnerability as the TEE OS is now able to check if the passed buffer is entirely contained in REE memory.
Vulnerability 3: restricted write to arbitrary address
As buf
can point to any arbitrary address, it's possible to write multiple restricted values to an arbitrary location with the TA memory, which can result in a crash of the TA or code execution in the context of the TA.
File: tzdemuxerservice.cpp
...
929 ! buf = (char *)param[0].memref.buffer;
930 ! size = param[0].memref.size;
...
1205 case CMD_TZDEMUXER_CLIENT_GET_PACKET:
1206 {
...
1292 ! packet_handle_s* retHandle = (packet_handle_s*)buf;
...
1308 ! retHandle->size = pkt.size;
1309 ! retHandle->handle = handle;
1310 ! retHandle->stream_index = pkt.stream_index;
1311 ! retHandle->flags = pkt.flags;
1312 ! retHandle->pts = pts;
1313 ! retHandle->duration = duration;
1314 ! retHandle->codec_type = sctx->streams[pkt.stream_index];
...
Samsung’s fix: Samsung indicated to us that the param[0]
parameter is now
verified to be TEE_PARAM_TYPE_MEMREF_INOUT
which is likely an adequate fix for this vulnerability as the TEE OS is now able to check if the passed buffer is entirely contained in REE memory.
Vulnerability 4: restricted write to arbitrary address
As buf
can point to any arbitrary address, it's possible to write multiple restricted values to an arbitrary location with the TA memory, which can result in a crash of the TA or code execution in the context of the TA.
File: tzdemuxerservice.cpp
...
929 ! buf = (char *)param[0].memref.buffer;
930 ! size = param[0].memref.size;
...
1322 case CMD_TZDEMUXER_CLIENT_GET_VIDEO_CODEC_INFO:
1323 {
...
1326 ! video_codec_info_s* codec_info = (video_codec_info_s*)buf;
1327 ! codec_info->codec_id = sctx->video_codec_info.codec_id;
1328 ! codec_info->width = sctx->video_codec_info.width;
1329 ! codec_info->height = sctx->video_codec_info.height;
1330 ! codec_info->framerate_num = sctx->video_codec_info.framerate_num;
1331 ! codec_info->framerate_den = sctx->video_codec_info.framerate_den;
...
Samsung’s fix: Samsung indicated to us that the param[0]
parameter is now
verified to be TEE_PARAM_TYPE_MEMREF_INOUT
which is likely an adequate fix for this vulnerability as the TEE OS is now able to check if the passed buffer is entirely contained in REE memory.
Vulnerability 5: restricted write to arbitrary address
As buf
can point to any arbitrary address, it's possible to write multiple restricted values to an arbitrary location with the TA memory, which can result in a crash of the TA or code execution in the context of the TA.
File: tzdemuxerservice.cpp
...
929 buf = (char *)param[0].memref.buffer;
930 size = param[0].memref.size;
...
1334 case CMD_TZDEMUXER_CLIENT_GET_AUDIO_CODEC_INFO:
1335 {
...
1337 audio_codec_info_s* codec_info = (audio_codec_info_s*)buf;
1338 codec_info->codec_id = sctx->audio_codec_info.codec_id;
1339 codec_info->sample_rate = sctx->audio_codec_info.sample_rate;
1340 codec_info->channels = sctx->audio_codec_info.channels;
1341 codec_info->framerate_num = sctx->audio_codec_info.framerate_num;
1342 codec_info->framerate_den = sctx->audio_codec_info.framerate_den;
...
Samsung’s fix: Samsung indicated to us that the param[0]
parameter is now
verified to be TEE_PARAM_TYPE_MEMREF_INOUT
which is likely an adequate fix for this vulnerability as the TEE OS is now able to check if the passed buffer is entirely contained in REE memory.
Vulnerability 6: return value of malloc not checked
The TA makes use of dynamic memory allocations using malloc
. It's a security best practice to verify the return value of a malloc
call in order to check if the request memory actually could be allocated. If it's not done, and the memory allocation fails, the subsequent writes to memory will be done to address 0
. This will cause a crash of the TA or potentially memory corruption of address 0
is mapped within the TA's memory space.
File: tzdemuxerservice.cpp
385 sub_buffer.data = malloc(needed_size);
386 memcpy((char*)sub_buffer.data,(char*)cur_node->buffer.data + skip , needed_size);
...
402 merge_buffer.data = malloc(needed_size);
403 //TA_PRINT("step 1\n");
404 memcpy((char*)merge_buffer.data, (char*)cur_node->buffer.data + skip , hsize - skip);
...
749 sctx = (tzdemuxer_service_s*)malloc(sizeof(tzdemuxer_service_s));
750 memset(sctx,0x0,sizeof(tzdemuxer_service_s));
...
797 sctx = (tzdemuxer_service_s*)malloc(sizeof(tzdemuxer_service_s));
798 memset(sctx,0x0,sizeof(tzdemuxer_service_s));
...
825 sctx->preset_info = malloc(size);
826 memcpy(sctx->preset_info,data,size);
...
964 buffer.data = malloc(size);
965 buffer.size = size;
966 memcpy(buffer.data,buf,size);
...
1009 buffer.data = malloc(size);
1010 buffer.size = size;
1011 memcpy(buffer.data,pBuffer,size);
Samsung's fix: Samsung indicated to us that the return value for each malloc
call is now correctly verified. This prevents the possibility for writing data to an unallocated (i.e. NULL) address.
Conclusion
We identified several critical vulnerabilities in the tzdemuxerservice TA developed for Samsung TVs (J-Series). The fixes for these vulnerabilities were pushed to the vulnerable TVs in 2016, but the updated source code was never made available publicly. Therefore, we could only determine the effectiveness of the fixes based on the information provided by Samsung via email. The parameter types are now correctly checked and therefore it's not possible any more to write controlled data to arbitrary addresses.