Commit e9b60476 authored by Amit Kachhap's avatar Amit Kachhap Committed by Will Deacon
Browse files

kselftest/arm64: Add utilities and a test to validate mte memory



This test checks that the memory tag is present after mte allocation and
the memory is accessible with those tags. This testcase verifies all
sync, async and none mte error reporting mode. The allocated mte buffers
are verified for Allocated range (no error expected while accessing
buffer), Underflow range, and Overflow range.

Different test scenarios covered here are,
* Verify that mte memory are accessible at byte/block level.
* Force underflow and overflow to occur and check the data consistency.
* Check to/from between tagged and untagged memory.
* Check that initial allocated memory to have 0 tag.

This change also creates the necessary infrastructure to add mte test
cases. MTE kselftests can use the several utility functions provided here
to add wide variety of mte test scenarios.

GCC compiler need flag '-march=armv8.5-a+memtag' so those flags are
verified before compilation.

The mte testcases can be launched with kselftest framework as,

make TARGETS=arm64 ARM64_SUBTARGETS=mte kselftest

or compiled as,

make -C tools/testing/selftests TARGETS=arm64 ARM64_SUBTARGETS=mte CC='compiler'

Co-developed-by: default avatarGabor Kertesz <gabor.kertesz@arm.com>
Signed-off-by: default avatarGabor Kertesz <gabor.kertesz@arm.com>
Signed-off-by: Amit Kachhap's avatarAmit Daniel Kachhap <amit.kachhap@arm.com>
Tested-by: Catalin Marinas's avatarCatalin Marinas <catalin.marinas@arm.com>
Acked-by: Catalin Marinas's avatarCatalin Marinas <catalin.marinas@arm.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20201002115630.24683-2-amit.kachhap@arm.com


Signed-off-by: default avatarWill Deacon <will@kernel.org>
parent f75aef39
......@@ -4,7 +4,7 @@
ARCH ?= $(shell uname -m 2>/dev/null || echo not)
ifneq (,$(filter $(ARCH),aarch64 arm64))
ARM64_SUBTARGETS ?= tags signal
ARM64_SUBTARGETS ?= tags signal mte
else
ARM64_SUBTARGETS :=
endif
......
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2020 ARM Limited
CFLAGS += -std=gnu99 -I.
SRCS := $(filter-out mte_common_util.c,$(wildcard *.c))
PROGS := $(patsubst %.c,%,$(SRCS))
#Add mte compiler option
ifneq ($(shell $(CC) --version 2>&1 | head -n 1 | grep gcc),)
CFLAGS += -march=armv8.5-a+memtag
endif
#check if the compiler works well
mte_cc_support := $(shell if ($(CC) $(CFLAGS) -E -x c /dev/null -o /dev/null 2>&1) then echo "1"; fi)
ifeq ($(mte_cc_support),1)
# Generated binaries to be installed by top KSFT script
TEST_GEN_PROGS := $(PROGS)
# Get Kernel headers installed and use them.
KSFT_KHDR_INSTALL := 1
endif
# Include KSFT lib.mk.
include ../../lib.mk
ifeq ($(mte_cc_support),1)
$(TEST_GEN_PROGS): mte_common_util.c mte_common_util.h mte_helper.S
endif
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 ARM Limited
#define _GNU_SOURCE
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "kselftest.h"
#include "mte_common_util.h"
#include "mte_def.h"
#define OVERFLOW_RANGE MT_GRANULE_SIZE
static int sizes[] = {
1, 555, 1033, MT_GRANULE_SIZE - 1, MT_GRANULE_SIZE,
/* page size - 1*/ 0, /* page_size */ 0, /* page size + 1 */ 0
};
enum mte_block_test_alloc {
UNTAGGED_TAGGED,
TAGGED_UNTAGGED,
TAGGED_TAGGED,
BLOCK_ALLOC_MAX,
};
static int check_buffer_by_byte(int mem_type, int mode)
{
char *ptr;
int i, j, item;
bool err;
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
item = sizeof(sizes)/sizeof(int);
for (i = 0; i < item; i++) {
ptr = (char *)mte_allocate_memory(sizes[i], mem_type, 0, true);
if (check_allocated_memory(ptr, sizes[i], mem_type, true) != KSFT_PASS)
return KSFT_FAIL;
mte_initialize_current_context(mode, (uintptr_t)ptr, sizes[i]);
/* Set some value in tagged memory */
for (j = 0; j < sizes[i]; j++)
ptr[j] = '1';
mte_wait_after_trig();
err = cur_mte_cxt.fault_valid;
/* Check the buffer whether it is filled. */
for (j = 0; j < sizes[i] && !err; j++) {
if (ptr[j] != '1')
err = true;
}
mte_free_memory((void *)ptr, sizes[i], mem_type, true);
if (err)
break;
}
if (!err)
return KSFT_PASS;
else
return KSFT_FAIL;
}
static int check_buffer_underflow_by_byte(int mem_type, int mode,
int underflow_range)
{
char *ptr;
int i, j, item, last_index;
bool err;
char *und_ptr = NULL;
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
item = sizeof(sizes)/sizeof(int);
for (i = 0; i < item; i++) {
ptr = (char *)mte_allocate_memory_tag_range(sizes[i], mem_type, 0,
underflow_range, 0);
if (check_allocated_memory_range(ptr, sizes[i], mem_type,
underflow_range, 0) != KSFT_PASS)
return KSFT_FAIL;
mte_initialize_current_context(mode, (uintptr_t)ptr, -underflow_range);
last_index = 0;
/* Set some value in tagged memory and make the buffer underflow */
for (j = sizes[i] - 1; (j >= -underflow_range) &&
(cur_mte_cxt.fault_valid == false); j--) {
ptr[j] = '1';
last_index = j;
}
mte_wait_after_trig();
err = false;
/* Check whether the buffer is filled */
for (j = 0; j < sizes[i]; j++) {
if (ptr[j] != '1') {
err = true;
ksft_print_msg("Buffer is not filled at index:%d of ptr:0x%lx\n",
j, ptr);
break;
}
}
if (err)
goto check_buffer_underflow_by_byte_err;
switch (mode) {
case MTE_NONE_ERR:
if (cur_mte_cxt.fault_valid == true || last_index != -underflow_range) {
err = true;
break;
}
/* There were no fault so the underflow area should be filled */
und_ptr = (char *) MT_CLEAR_TAG((size_t) ptr - underflow_range);
for (j = 0 ; j < underflow_range; j++) {
if (und_ptr[j] != '1') {
err = true;
break;
}
}
break;
case MTE_ASYNC_ERR:
/* Imprecise fault should occur otherwise return error */
if (cur_mte_cxt.fault_valid == false) {
err = true;
break;
}
/*
* The imprecise fault is checked after the write to the buffer,
* so the underflow area before the fault should be filled.
*/
und_ptr = (char *) MT_CLEAR_TAG((size_t) ptr);
for (j = last_index ; j < 0 ; j++) {
if (und_ptr[j] != '1') {
err = true;
break;
}
}
break;
case MTE_SYNC_ERR:
/* Precise fault should occur otherwise return error */
if (!cur_mte_cxt.fault_valid || (last_index != (-1))) {
err = true;
break;
}
/* Underflow area should not be filled */
und_ptr = (char *) MT_CLEAR_TAG((size_t) ptr);
if (und_ptr[-1] == '1')
err = true;
break;
default:
err = true;
break;
}
check_buffer_underflow_by_byte_err:
mte_free_memory_tag_range((void *)ptr, sizes[i], mem_type, underflow_range, 0);
if (err)
break;
}
return (err ? KSFT_FAIL : KSFT_PASS);
}
static int check_buffer_overflow_by_byte(int mem_type, int mode,
int overflow_range)
{
char *ptr;
int i, j, item, last_index;
bool err;
size_t tagged_size, overflow_size;
char *over_ptr = NULL;
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
item = sizeof(sizes)/sizeof(int);
for (i = 0; i < item; i++) {
ptr = (char *)mte_allocate_memory_tag_range(sizes[i], mem_type, 0,
0, overflow_range);
if (check_allocated_memory_range(ptr, sizes[i], mem_type,
0, overflow_range) != KSFT_PASS)
return KSFT_FAIL;
tagged_size = MT_ALIGN_UP(sizes[i]);
mte_initialize_current_context(mode, (uintptr_t)ptr, sizes[i] + overflow_range);
/* Set some value in tagged memory and make the buffer underflow */
for (j = 0, last_index = 0 ; (j < (sizes[i] + overflow_range)) &&
(cur_mte_cxt.fault_valid == false); j++) {
ptr[j] = '1';
last_index = j;
}
mte_wait_after_trig();
err = false;
/* Check whether the buffer is filled */
for (j = 0; j < sizes[i]; j++) {
if (ptr[j] != '1') {
err = true;
ksft_print_msg("Buffer is not filled at index:%d of ptr:0x%lx\n",
j, ptr);
break;
}
}
if (err)
goto check_buffer_overflow_by_byte_err;
overflow_size = overflow_range - (tagged_size - sizes[i]);
switch (mode) {
case MTE_NONE_ERR:
if ((cur_mte_cxt.fault_valid == true) ||
(last_index != (sizes[i] + overflow_range - 1))) {
err = true;
break;
}
/* There were no fault so the overflow area should be filled */
over_ptr = (char *) MT_CLEAR_TAG((size_t) ptr + tagged_size);
for (j = 0 ; j < overflow_size; j++) {
if (over_ptr[j] != '1') {
err = true;
break;
}
}
break;
case MTE_ASYNC_ERR:
/* Imprecise fault should occur otherwise return error */
if (cur_mte_cxt.fault_valid == false) {
err = true;
break;
}
/*
* The imprecise fault is checked after the write to the buffer,
* so the overflow area should be filled before the fault.
*/
over_ptr = (char *) MT_CLEAR_TAG((size_t) ptr);
for (j = tagged_size ; j < last_index; j++) {
if (over_ptr[j] != '1') {
err = true;
break;
}
}
break;
case MTE_SYNC_ERR:
/* Precise fault should occur otherwise return error */
if (!cur_mte_cxt.fault_valid || (last_index != tagged_size)) {
err = true;
break;
}
/* Underflow area should not be filled */
over_ptr = (char *) MT_CLEAR_TAG((size_t) ptr + tagged_size);
for (j = 0 ; j < overflow_size; j++) {
if (over_ptr[j] == '1')
err = true;
}
break;
default:
err = true;
break;
}
check_buffer_overflow_by_byte_err:
mte_free_memory_tag_range((void *)ptr, sizes[i], mem_type, 0, overflow_range);
if (err)
break;
}
return (err ? KSFT_FAIL : KSFT_PASS);
}
static int check_buffer_by_block_iterate(int mem_type, int mode, size_t size)
{
char *src, *dst;
int j, result = KSFT_PASS;
enum mte_block_test_alloc alloc_type = UNTAGGED_TAGGED;
for (alloc_type = UNTAGGED_TAGGED; alloc_type < (int) BLOCK_ALLOC_MAX; alloc_type++) {
switch (alloc_type) {
case UNTAGGED_TAGGED:
src = (char *)mte_allocate_memory(size, mem_type, 0, false);
if (check_allocated_memory(src, size, mem_type, false) != KSFT_PASS)
return KSFT_FAIL;
dst = (char *)mte_allocate_memory(size, mem_type, 0, true);
if (check_allocated_memory(dst, size, mem_type, true) != KSFT_PASS) {
mte_free_memory((void *)src, size, mem_type, false);
return KSFT_FAIL;
}
break;
case TAGGED_UNTAGGED:
dst = (char *)mte_allocate_memory(size, mem_type, 0, false);
if (check_allocated_memory(dst, size, mem_type, false) != KSFT_PASS)
return KSFT_FAIL;
src = (char *)mte_allocate_memory(size, mem_type, 0, true);
if (check_allocated_memory(src, size, mem_type, true) != KSFT_PASS) {
mte_free_memory((void *)dst, size, mem_type, false);
return KSFT_FAIL;
}
break;
case TAGGED_TAGGED:
src = (char *)mte_allocate_memory(size, mem_type, 0, true);
if (check_allocated_memory(src, size, mem_type, true) != KSFT_PASS)
return KSFT_FAIL;
dst = (char *)mte_allocate_memory(size, mem_type, 0, true);
if (check_allocated_memory(dst, size, mem_type, true) != KSFT_PASS) {
mte_free_memory((void *)src, size, mem_type, true);
return KSFT_FAIL;
}
break;
default:
return KSFT_FAIL;
}
cur_mte_cxt.fault_valid = false;
result = KSFT_PASS;
mte_initialize_current_context(mode, (uintptr_t)dst, size);
/* Set some value in memory and copy*/
memset((void *)src, (int)'1', size);
memcpy((void *)dst, (void *)src, size);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid) {
result = KSFT_FAIL;
goto check_buffer_by_block_err;
}
/* Check the buffer whether it is filled. */
for (j = 0; j < size; j++) {
if (src[j] != dst[j] || src[j] != '1') {
result = KSFT_FAIL;
break;
}
}
check_buffer_by_block_err:
mte_free_memory((void *)src, size, mem_type,
MT_FETCH_TAG((uintptr_t)src) ? true : false);
mte_free_memory((void *)dst, size, mem_type,
MT_FETCH_TAG((uintptr_t)dst) ? true : false);
if (result != KSFT_PASS)
return result;
}
return result;
}
static int check_buffer_by_block(int mem_type, int mode)
{
int i, item, result = KSFT_PASS;
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
item = sizeof(sizes)/sizeof(int);
cur_mte_cxt.fault_valid = false;
for (i = 0; i < item; i++) {
result = check_buffer_by_block_iterate(mem_type, mode, sizes[i]);
if (result != KSFT_PASS)
break;
}
return result;
}
static int compare_memory_tags(char *ptr, size_t size, int tag)
{
int i, new_tag;
for (i = 0 ; i < size ; i += MT_GRANULE_SIZE) {
new_tag = MT_FETCH_TAG((uintptr_t)(mte_get_tag_address(ptr + i)));
if (tag != new_tag) {
ksft_print_msg("FAIL: child mte tag mismatch\n");
return KSFT_FAIL;
}
}
return KSFT_PASS;
}
static int check_memory_initial_tags(int mem_type, int mode, int mapping)
{
char *ptr;
int run, fd;
int total = sizeof(sizes)/sizeof(int);
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
for (run = 0; run < total; run++) {
/* check initial tags for anonymous mmap */
ptr = (char *)mte_allocate_memory(sizes[run], mem_type, mapping, false);
if (check_allocated_memory(ptr, sizes[run], mem_type, false) != KSFT_PASS)
return KSFT_FAIL;
if (compare_memory_tags(ptr, sizes[run], 0) != KSFT_PASS) {
mte_free_memory((void *)ptr, sizes[run], mem_type, false);
return KSFT_FAIL;
}
mte_free_memory((void *)ptr, sizes[run], mem_type, false);
/* check initial tags for file mmap */
fd = create_temp_file();
if (fd == -1)
return KSFT_FAIL;
ptr = (char *)mte_allocate_file_memory(sizes[run], mem_type, mapping, false, fd);
if (check_allocated_memory(ptr, sizes[run], mem_type, false) != KSFT_PASS) {
close(fd);
return KSFT_FAIL;
}
if (compare_memory_tags(ptr, sizes[run], 0) != KSFT_PASS) {
mte_free_memory((void *)ptr, sizes[run], mem_type, false);
close(fd);
return KSFT_FAIL;
}
mte_free_memory((void *)ptr, sizes[run], mem_type, false);
close(fd);
}
return KSFT_PASS;
}
int main(int argc, char *argv[])
{
int err;
size_t page_size = getpagesize();
int item = sizeof(sizes)/sizeof(int);
sizes[item - 3] = page_size - 1;
sizes[item - 2] = page_size;
sizes[item - 1] = page_size + 1;
err = mte_default_setup();
if (err)
return err;
/* Register SIGSEGV handler */
mte_register_signal(SIGSEGV, mte_default_handler);
/* Buffer by byte tests */
evaluate_test(check_buffer_by_byte(USE_MMAP, MTE_SYNC_ERR),
"Check buffer correctness by byte with sync err mode and mmap memory\n");
evaluate_test(check_buffer_by_byte(USE_MMAP, MTE_ASYNC_ERR),
"Check buffer correctness by byte with async err mode and mmap memory\n");
evaluate_test(check_buffer_by_byte(USE_MPROTECT, MTE_SYNC_ERR),
"Check buffer correctness by byte with sync err mode and mmap/mprotect memory\n");
evaluate_test(check_buffer_by_byte(USE_MPROTECT, MTE_ASYNC_ERR),
"Check buffer correctness by byte with async err mode and mmap/mprotect memory\n");
/* Check buffer underflow with underflow size as 16 */
evaluate_test(check_buffer_underflow_by_byte(USE_MMAP, MTE_SYNC_ERR, MT_GRANULE_SIZE),
"Check buffer write underflow by byte with sync mode and mmap memory\n");
evaluate_test(check_buffer_underflow_by_byte(USE_MMAP, MTE_ASYNC_ERR, MT_GRANULE_SIZE),
"Check buffer write underflow by byte with async mode and mmap memory\n");
evaluate_test(check_buffer_underflow_by_byte(USE_MMAP, MTE_NONE_ERR, MT_GRANULE_SIZE),
"Check buffer write underflow by byte with tag check fault ignore and mmap memory\n");
/* Check buffer underflow with underflow size as page size */
evaluate_test(check_buffer_underflow_by_byte(USE_MMAP, MTE_SYNC_ERR, page_size),
"Check buffer write underflow by byte with sync mode and mmap memory\n");
evaluate_test(check_buffer_underflow_by_byte(USE_MMAP, MTE_ASYNC_ERR, page_size),
"Check buffer write underflow by byte with async mode and mmap memory\n");
evaluate_test(check_buffer_underflow_by_byte(USE_MMAP, MTE_NONE_ERR, page_size),
"Check buffer write underflow by byte with tag check fault ignore and mmap memory\n");
/* Check buffer overflow with overflow size as 16 */
evaluate_test(check_buffer_overflow_by_byte(USE_MMAP, MTE_SYNC_ERR, MT_GRANULE_SIZE),
"Check buffer write overflow by byte with sync mode and mmap memory\n");
evaluate_test(check_buffer_overflow_by_byte(USE_MMAP, MTE_ASYNC_ERR, MT_GRANULE_SIZE),
"Check buffer write overflow by byte with async mode and mmap memory\n");
evaluate_test(check_buffer_overflow_by_byte(USE_MMAP, MTE_NONE_ERR, MT_GRANULE_SIZE),
"Check buffer write overflow by byte with tag fault ignore mode and mmap memory\n");
/* Buffer by block tests */
evaluate_test(check_buffer_by_block(USE_MMAP, MTE_SYNC_ERR),
"Check buffer write correctness by block with sync mode and mmap memory\n");
evaluate_test(check_buffer_by_block(USE_MMAP, MTE_ASYNC_ERR),
"Check buffer write correctness by block with async mode and mmap memory\n");
evaluate_test(check_buffer_by_block(USE_MMAP, MTE_NONE_ERR),
"Check buffer write correctness by block with tag fault ignore and mmap memory\n");
/* Initial tags are supposed to be 0 */
evaluate_test(check_memory_initial_tags(USE_MMAP, MTE_SYNC_ERR, MAP_PRIVATE),
"Check initial tags with private mapping, sync error mode and mmap memory\n");
evaluate_test(check_memory_initial_tags(USE_MPROTECT, MTE_SYNC_ERR, MAP_PRIVATE),
"Check initial tags with private mapping, sync error mode and mmap/mprotect memory\n");
evaluate_test(check_memory_initial_tags(USE_MMAP, MTE_SYNC_ERR, MAP_SHARED),
"Check initial tags with shared mapping, sync error mode and mmap memory\n");
evaluate_test(check_memory_initial_tags(USE_MPROTECT, MTE_SYNC_ERR, MAP_SHARED),
"Check initial tags with shared mapping, sync error mode and mmap/mprotect memory\n");
mte_restore_setup();
ksft_print_cnts();
return ksft_get_fail_cnt() == 0 ? KSFT_PASS : KSFT_FAIL;
}
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 ARM Limited
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/auxvec.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <asm/hwcap.h>
#include "kselftest.h"
#include "mte_common_util.h"
#include "mte_def.h"
#define INIT_BUFFER_SIZE 256
struct mte_fault_cxt cur_mte_cxt;
static unsigned int mte_cur_mode;
static unsigned int mte_cur_pstate_tco;
void mte_default_handler(int signum, siginfo_t *si, void *uc)
{
unsigned long addr = (unsigned long)si->si_addr;
if (signum == SIGSEGV) {
#ifdef DEBUG
ksft_print_msg("INFO: SIGSEGV signal at pc=%lx, fault addr=%lx, si_code=%lx\n",
((ucontext_t *)uc)->uc_mcontext.pc, addr, si->si_code);
#endif
if (si->si_code == SEGV_MTEAERR) {
if (cur_mte_cxt.trig_si_code == si->si_code)
cur_mte_cxt.fault_valid = true;
return;
}
/* Compare the context for precise error */
else if (si->si_code == SEGV_MTESERR) {
if (cur_mte_cxt.trig_si_code == si->si_code &&
((cur_mte_cxt.trig_range >= 0 &&
addr >= MT_CLEAR_TAG(cur_mte_cxt.trig_addr) &&
addr <= (MT_CLEAR_TAG(cur_mte_cxt.trig_addr) + cur_mte_cxt.trig_range)) ||
(cur_mte_cxt.trig_range < 0 &&
addr <= MT_CLEAR_TAG(cur_mte_cxt.trig_addr) &&
addr >= (MT_CLEAR_TAG(cur_mte_cxt.trig_addr) + cur_mte_cxt.trig_range)))) {
cur_mte_cxt.fault_valid = true;
/* Adjust the pc by 4 */
((ucontext_t *)uc)->uc_mcontext.pc += 4;
} else {
ksft_print_msg("Invalid MTE synchronous exception caught!\n");
exit(1);
}
} else {
ksft_print_msg("Unknown SIGSEGV exception caught!\n");
exit(1);
}
} else if (signum == SIGBUS) {
ksft_print_msg("INFO: SIGBUS signal at pc=%lx, fault addr=%lx, si_code=%lx\n",
((ucontext_t *)uc)->uc_mcontext.pc, addr, si->si_code);
if ((cur_mte_cxt.trig_range >= 0