/*
 * Copyright (C) 2023 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 <mali_gpu_utils.h>

#include "../includes/common.h"

struct base_mem_import_user_buffer {
    __u64 ptr;
    __u64 length;
};

union kbase_ioctl_mem_import {
    struct {
        __u64 flags;
        __u64 phandle;
        __u32 type;
        __u32 padding;
    } in;
    struct {
        __u64 flags;
        __u64 gpu_va;
        __u64 va_pages;
    } out;
};

#define KBASE_IOCTL_MEM_IMPORT _IOWR(KBASE_IOCTL_TYPE, 22, union kbase_ioctl_mem_import)

int main(void) {
    const size_t page_size = getpagesize();
    int mali_fd = open("/dev/mali0", O_RDWR);
    if (mali_fd < 0) {
        printf("Failed to open /dev/mali0!");
        return EXIT_FAILURE;
    }

    mali_gpu_info gpu_info = {
            .is_csf = false,
            .version = 0,
            .tracking_page = NULL,
    };

    // This bug affects both CSF and non-CSF versions. Hence, version check is not needed
    if (initialize_mali_gpu(mali_fd, &gpu_info) == EXIT_FAILURE) {
        return EXIT_FAILURE;
    }

    // make a single-page anonymous mapping
    void *anon_mapping =
            mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (anon_mapping == MAP_FAILED) {
        printf("mmap failed!");
        teardown(&gpu_info);
        return EXIT_FAILURE;
    }

    // Create a KBASE_MEM_TYPE_IMPORTED_USER_BUF that refers to the anonymous
    // mapping's address range.
    struct base_mem_import_user_buffer ubuf = {.ptr = (unsigned long)anon_mapping, .length = 128};
    union kbase_ioctl_mem_import mi = {.in = {
                                               .flags = 0xf /* CPU+GPU R+W */,
                                               .phandle = (unsigned long)&ubuf,
                                               .type = 3 /* BASE_MEM_IMPORT_TYPE_USER_BUFFER */
                                       }};

    // USER_BUFFER must be CPU cached. Without fix, the user buffer mapping is created.
    // With fix, the ioctl call fails and returns an error value
    int result = ioctl(mali_fd, KBASE_IOCTL_MEM_IMPORT, &mi);

    // free the allocated memory
    munmap(anon_mapping, page_size);
    teardown(&gpu_info);

    if (result == 0) {
        printf("Driver vulnerable to b/296910715!");
        return EXIT_VULNERABLE;
    }
    return EXIT_SUCCESS;
}
