SDK_PF_Write

April 9, 2021 at 11:03 am
Analysis Artifact SGX

See also: C++ fwrite

Note that write only do memory cope but doesn't actually write to the disk.

  1. check data to write within enclave
  2. check if the destination can be written
  3. check if there is empty place left in the meta data region. If so then write
  4. write data to the data node(file_data_node_t) iteratively, see below
  5. return count

Write data

  1. create a new data node if needed
  2. write to file_data_node->plain.data[offset_in_node]
  3. update file size
  4. update writing status and mht (Merkle Hash Tree) info
size_t protected_fs_file::write(const void* ptr, size_t size, size_t count)
{
    if (ptr == NULL || size == 0 || count == 0)
        return 0;

    int32_t result32 = sgx_thread_mutex_lock(&mutex);
    if (result32 != 0)
    {
        last_error = result32;
        file_status = SGX_FILE_STATUS_MEMORY_CORRUPTED;
        return 0;
    }

    size_t data_left_to_write = size * count;

    // prevent overlap...
#if defined(_WIN64) || defined(__x86_64__)
    if (size > UINT32_MAX || count > UINT32_MAX)
    {
        last_error = EINVAL;
        sgx_thread_mutex_unlock(&mutex);
        return 0;
    }
#else
    if (((uint64_t)((uint64_t)size * (uint64_t)count)) != (uint64_t)data_left_to_write)
    {
        last_error = EINVAL;
        sgx_thread_mutex_unlock(&mutex);
        return 0;
    }
#endif

    if (sgx_is_outside_enclave(ptr, data_left_to_write))
    {
        last_error = SGX_ERROR_INVALID_PARAMETER;
        sgx_thread_mutex_unlock(&mutex);
        return 0;
    }

    if (file_status != SGX_FILE_STATUS_OK)
    {
        last_error = SGX_ERROR_FILE_BAD_STATUS;
        sgx_thread_mutex_unlock(&mutex);
        return 0;
    }

    if (open_mode.append == 0 && open_mode.update == 0 && open_mode.write == 0)
    {
        last_error = EACCES;
        sgx_thread_mutex_unlock(&mutex);
        return 0;
    }

    if (open_mode.append == 1)
        offset = encrypted_part_plain.size; // add at the end of the file

    const unsigned char* data_to_write = (const unsigned char*)ptr;

    // the first block of user data is written in the meta-data encrypted part
    if (offset < MD_USER_DATA_SIZE)
    {
        size_t empty_place_left_in_md = MD_USER_DATA_SIZE - (size_t)offset; // offset is smaller than MD_USER_DATA_SIZE
        if (data_left_to_write <= empty_place_left_in_md)
        {
            memcpy(&encrypted_part_plain.data[offset], data_to_write, data_left_to_write);
            offset += data_left_to_write;
            data_to_write += data_left_to_write; // not needed, to prevent future errors
            data_left_to_write = 0;
        }
        else
        {
            memcpy(&encrypted_part_plain.data[offset], data_to_write, empty_place_left_in_md);
            offset += empty_place_left_in_md;
            data_to_write += empty_place_left_in_md;
            data_left_to_write -= empty_place_left_in_md;
        }
        
        if (offset > encrypted_part_plain.size)
            encrypted_part_plain.size = offset; // file grew, update the new file size

        need_writing = true;
    }

    while (data_left_to_write > 0)
    {
        file_data_node_t* file_data_node = NULL;
        file_data_node = get_data_node(); // return the data node of the current offset, will read it from disk or create new one if needed (and also the mht node if needed)
        if (file_data_node == NULL)
            break;

        size_t offset_in_node = (size_t)((offset - MD_USER_DATA_SIZE) % NODE_SIZE);
        size_t empty_place_left_in_node = NODE_SIZE - offset_in_node;
        
        if (data_left_to_write <= empty_place_left_in_node)
        { // this will be the last write
            memcpy(&file_data_node->plain.data[offset_in_node], data_to_write, data_left_to_write);
            offset += data_left_to_write;
            data_to_write += data_left_to_write; // not needed, to prevent future errors
            data_left_to_write = 0;
        }
        else
        {
            memcpy(&file_data_node->plain.data[offset_in_node], data_to_write, empty_place_left_in_node);
            offset += empty_place_left_in_node;
            data_to_write += empty_place_left_in_node;
            data_left_to_write -= empty_place_left_in_node;

        }

        if (offset > encrypted_part_plain.size)
            encrypted_part_plain.size = offset; // file grew, update the new file size

        if (file_data_node->need_writing == false)
        {
            file_data_node->need_writing = true;
            file_mht_node_t* file_mht_node = file_data_node->parent;
            while (file_mht_node->mht_node_number != 0) // set all the mht parent nodes as 'need writing'
            {
                file_mht_node->need_writing = true;
                file_mht_node = file_mht_node->parent;
            }
            root_mht.need_writing = true;
            need_writing = true;
        }
    }

    sgx_thread_mutex_unlock(&mutex);

    size_t ret_count = ((size * count) - data_left_to_write) / size;
    return ret_count;
}