Vulnerable tzdemuxerservice TA on Samsung TVs (J-series)

Monday, Nov 1, 2021


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.