diff --git a/host/xtest/regression_1000.c b/host/xtest/regression_1000.c index 18b15b516..77f1007d1 100644 --- a/host/xtest/regression_1000.c +++ b/host/xtest/regression_1000.c @@ -46,6 +46,8 @@ #include "xtest_helpers.h" #include "xtest_test.h" #include "xtest_uuid_helpers.h" +#define RISCV_VECTOR_CTX_IMPLEMENTATION +#include #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -3475,3 +3477,271 @@ static void xtest_tee_test_1042(ADBG_Case_t *c) } ADBG_CASE_DEFINE(regression, 1042, xtest_tee_test_1042, "Test ASAN (Memory address sanitizer)"); + + +/* + * RISC-V vector context switching. + * + * A TA runs with the vector unit disabled and is given it on the first + * vector instruction, or vector CSR access, it executes, while the normal + * world context is switched whenever a thread crosses into the TEE and + * back. Both halves are checked here. + */ + +static TEEC_Result vec_invoke(TEEC_Session *session, uint32_t subtest, + uint32_t seed, uint32_t *bad_reg, + uint32_t *vlenb, uint32_t *ret_orig) +{ + TEEC_Operation op = { }; + TEEC_Result res = TEEC_ERROR_GENERIC; + + op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_OUTPUT, + TEEC_NONE, TEEC_NONE); + op.params[0].value.a = subtest; + op.params[0].value.b = seed; + + res = TEEC_InvokeCommand(session, TA_OS_TEST_CMD_RISCV_VEC_CONTEXT, + &op, ret_orig); + if (bad_reg) + *bad_reg = op.params[1].value.a; + if (vlenb) + *vlenb = op.params[1].value.b; + + return res; +} + +static void vec_log_bad_reg(uint32_t reg) +{ + if (reg == 32) + Do_ADBG_Log(" the vector CSRs were not preserved"); + else + Do_ADBG_Log(" v%u was the first register not preserved", + reg); +} + +static void vec_check_ta_subtest(ADBG_Case_t *c, TEEC_Session *session, + uint32_t subtest, uint32_t seed) +{ + uint32_t ret_orig = 0; + uint32_t bad_reg = 0; + uint32_t vlenb = 0; + TEEC_Result res = TEEC_ERROR_GENERIC; + + res = vec_invoke(session, subtest, seed, &bad_reg, &vlenb, &ret_orig); + if (!ADBG_EXPECT_TEEC_SUCCESS(c, res)) + vec_log_bad_reg(bad_reg); +} + +#ifdef RISCV_VECTOR_CTX_SUPPORTED +struct vec_ree_arg { + TEEC_Session *session; + uint32_t subtest; + uint32_t seed; + uint32_t bad_reg; + uint32_t vlenb; + uint32_t ret_orig; +}; + +static unsigned long vec_ree_invoke(void *a) +{ + struct vec_ree_arg *arg = a; + + return vec_invoke(arg->session, arg->subtest, arg->seed, &arg->bad_reg, + &arg->vlenb, &arg->ret_orig); +} + +/* + * Holds a pattern in this process' vector registers across an invoke and + * checks that the TEE gave them back. This is the normal world half of the + * domain switch. + * + * The vector calling convention allows any call to clobber any vector + * register, so strictly this only holds because neither libteec nor the + * kernel's syscall path uses vector. That is true of both today and is + * what makes the check worth having: if the TEE fails to put the normal + * world context back, this is where it shows. + */ +static void vec_check_ree_preserved(ADBG_Case_t *c, TEEC_Session *session, + uint32_t subtest, uint32_t seed) +{ + struct riscv_vector_ctx *expect = NULL; + struct riscv_vector_ctx *got = NULL; + struct vec_ree_arg arg = { }; + TEEC_Result res = TEEC_ERROR_GENERIC; + unsigned long vlenb = riscv_vector_ctx_vlenb(); + int diff = 0; + + if (!ADBG_EXPECT_COMPARE_UNSIGNED(c, vlenb, <=, + (unsigned long) + RISCV_VECTOR_CTX_VLENB_MAX)) + return; + + expect = calloc(1, sizeof(*expect)); + got = calloc(1, sizeof(*got)); + if (!ADBG_EXPECT_NOT_NULL(c, expect) || + !ADBG_EXPECT_NOT_NULL(c, got)) + goto out; + + arg.session = session; + arg.subtest = subtest; + arg.seed = seed; + + riscv_vector_ctx_pattern(expect, seed, vlenb); + + res = (TEEC_Result)riscv_vector_ctx_roundtrip(expect, got, + vec_ree_invoke, &arg); + if (!ADBG_EXPECT_TEEC_SUCCESS(c, res)) { + vec_log_bad_reg(arg.bad_reg); + goto out; + } + + diff = riscv_vector_ctx_diff(expect, got, vlenb); + if (!ADBG_EXPECT_COMPARE_SIGNED(c, diff, ==, -1)) + vec_log_bad_reg((uint32_t)diff); +out: + free(expect); + free(got); +} +#endif /*RISCV_VECTOR_CTX_SUPPORTED*/ + +struct test_1046_thread_arg { + pthread_t thr; + uint32_t seed; + TEEC_Result res; + uint32_t bad_reg; +}; + +static void *test_1046_thread(void *a) +{ + struct test_1046_thread_arg *arg = a; + TEEC_Session session = { }; + uint32_t ret_orig = 0; + size_t n = 0; + + arg->res = xtest_teec_open_session(&session, &os_test_ta_uuid, NULL, + &ret_orig); + if (arg->res != TEEC_SUCCESS) + return NULL; + + for (n = 0; n < 4; n++) { + arg->res = vec_invoke(&session, TA_RISCV_VEC_SUBTEST_SYSCALL, + arg->seed, &arg->bad_reg, NULL, + &ret_orig); + if (arg->res != TEEC_SUCCESS) + break; + } + + TEEC_CloseSession(&session); + + return NULL; +} + +static void xtest_tee_test_1046(ADBG_Case_t *c) +{ + struct test_1046_thread_arg arg[NUM_THREADS] = { }; + TEEC_Session session = { }; + TEEC_Session tainted = { }; + uint32_t ret_orig = 0; + uint32_t bad_reg = 0; + uint32_t vlenb = 0; + TEEC_Result res = TEEC_ERROR_GENERIC; + size_t nt = NUM_THREADS; + size_t n = 0; + + if (!ADBG_EXPECT_TEEC_SUCCESS(c, + xtest_teec_open_session(&session, &os_test_ta_uuid, + NULL, &ret_orig))) + return; + + res = vec_invoke(&session, TA_RISCV_VEC_SUBTEST_SYSCALL, 0x11, + &bad_reg, &vlenb, &ret_orig); + if (res == TEEC_ERROR_NOT_SUPPORTED) { + Do_ADBG_Log("TA has no RISC-V vector context test - skip"); + goto out; + } + Do_ADBG_Log(" vlenb %u, VLEN %u", vlenb, vlenb * 8); + + Do_ADBG_BeginSubCase(c, "TA context across a syscall"); + if (!ADBG_EXPECT_TEEC_SUCCESS(c, res)) + vec_log_bad_reg(bad_reg); + Do_ADBG_EndSubCase(c, "TA context across a syscall"); + + /* + * Reading a vector CSR traps with VS Off exactly as a vector + * instruction does, so a TEE that decodes only the instructions + * kills the TA here instead of handing it a context. + */ + Do_ADBG_BeginSubCase(c, "TA reads a vector CSR"); + vec_check_ta_subtest(c, &session, TA_RISCV_VEC_SUBTEST_CSR_FIRST, + 0x22); + Do_ADBG_EndSubCase(c, "TA reads a vector CSR"); + + Do_ADBG_BeginSubCase(c, "TA context is not carried between instances"); + res = xtest_teec_open_session(&tainted, &os_test_ta_uuid, NULL, + &ret_orig); + if (ADBG_EXPECT_TEEC_SUCCESS(c, res)) { + ADBG_EXPECT_TEEC_SUCCESS(c, + vec_invoke(&tainted, TA_RISCV_VEC_SUBTEST_TAINT, 0x33, + NULL, NULL, &ret_orig)); + TEEC_CloseSession(&tainted); + + memset(&tainted, 0, sizeof(tainted)); + res = xtest_teec_open_session(&tainted, &os_test_ta_uuid, NULL, + &ret_orig); + if (ADBG_EXPECT_TEEC_SUCCESS(c, res)) { + vec_check_ta_subtest(c, &tainted, + TA_RISCV_VEC_SUBTEST_CHECK_TAINT, + 0x33); + TEEC_CloseSession(&tainted); + } + } + Do_ADBG_EndSubCase(c, "TA context is not carried between instances"); + +#ifdef RISCV_VECTOR_CTX_SUPPORTED + /* + * The TEE saves the normal world context on the way in. On the way + * out it puts it back, unless nothing in the TEE ever enabled the + * vector unit, in which case the registers were never disturbed and + * the restore is skipped. Cover both with a TA command that uses + * vector and one that does not. + */ + Do_ADBG_BeginSubCase(c, "REE context across an invoke using vector"); + vec_check_ree_preserved(c, &session, TA_RISCV_VEC_SUBTEST_SYSCALL, + 0x44); + Do_ADBG_EndSubCase(c, "REE context across an invoke using vector"); + + Do_ADBG_BeginSubCase(c, "REE context across an invoke not using it"); + vec_check_ree_preserved(c, &session, TA_RISCV_VEC_SUBTEST_NO_VECTOR, + 0x55); + Do_ADBG_EndSubCase(c, "REE context across an invoke not using it"); +#else /*RISCV_VECTOR_CTX_SUPPORTED*/ + /* + * xtest itself has to be built for a hart with the vector extension + * to hold a pattern in the vector registers across the call, which + * is a property of the toolchain the normal world was built with, + * not of what the TEE supports. + */ + Do_ADBG_Log("xtest built without the vector extension - the normal " + "world half of the domain switch is not checked"); +#endif /*RISCV_VECTOR_CTX_SUPPORTED*/ + + Do_ADBG_BeginSubCase(c, "Concurrent TA contexts"); + for (n = 0; n < nt; n++) { + arg[n].seed = 0x80 + n; + if (!ADBG_EXPECT(c, 0, pthread_create(&arg[n].thr, NULL, + test_1046_thread, + arg + n))) + nt = n; /* break loop and start cleanup */ + } + for (n = 0; n < nt; n++) { + ADBG_EXPECT(c, 0, pthread_join(arg[n].thr, NULL)); + if (!ADBG_EXPECT_TEEC_SUCCESS(c, arg[n].res)) + vec_log_bad_reg(arg[n].bad_reg); + } + Do_ADBG_EndSubCase(c, "Concurrent TA contexts"); + +out: + TEEC_CloseSession(&session); +} +ADBG_CASE_DEFINE(regression, 1046, xtest_tee_test_1046, + "Test RISC-V vector context switching"); diff --git a/ta/os_test/include/os_test.h b/ta/os_test/include/os_test.h index cddcc604d..a7c917007 100644 --- a/ta/os_test/include/os_test.h +++ b/ta/os_test/include/os_test.h @@ -51,5 +51,7 @@ TEE_Result ta_entry_asan_global(void); TEE_Result ta_entry_asan_malloc(void); TEE_Result ta_entry_asan_memfunc(void); TEE_Result ta_entry_asan_uaf(void); +TEE_Result ta_entry_riscv_vec_context(uint32_t param_types, + TEE_Param params[4]); #endif /*OS_TEST_H */ diff --git a/ta/os_test/include/riscv_vector_ctx.h b/ta/os_test/include/riscv_vector_ctx.h new file mode 100644 index 000000000..21092fa52 --- /dev/null +++ b/ta/os_test/include/riscv_vector_ctx.h @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2026, RISCStar Solutions Limited + */ + +#ifndef RISCV_VECTOR_CTX_H +#define RISCV_VECTOR_CTX_H + +#if defined(__riscv) && defined(__riscv_v) +#define RISCV_VECTOR_CTX_SUPPORTED 1 + +#include +#include + +/* + * Widest vector register these tests are built for. VLEN is discovered at + * run time from vlenb, so the buffers are sized for the largest register + * width worth carrying and only the first vlenb bytes of each register are + * ever looked at. + */ +#define RISCV_VECTOR_CTX_VLENB_MAX 128 +#define RISCV_VECTOR_CTX_NUM_REGS 32 + +/* + * Unlike the floating-point calling convention, the vector one has no + * callee-saved vector registers at all: v0..v31 and the vector CSRs may + * legitimately be clobbered by any call. A test therefore cannot check the + * vector context across an ordinary C call and learn anything, which is why + * riscv_vector_ctx_syscall() below reaches the TEE through a bare ecall + * with no compiler-generated code between installing the context and + * reading it back. + * + * The layout is shared with the assembly at the bottom of this header: + * 0 vl + * 8 vtype + * 16 vcsr + * 24 vstart + * 32 vregs + */ +struct riscv_vector_ctx { + unsigned long vl; + unsigned long vtype; + unsigned long vcsr; + unsigned long vstart; + uint8_t vregs[RISCV_VECTOR_CTX_NUM_REGS * RISCV_VECTOR_CTX_VLENB_MAX]; +}; + +/* Width of one vector register in bytes. Traps if vector is disabled. */ +unsigned long riscv_vector_ctx_vlenb(void); + +/* Installs @in, leaving it in the registers */ +void riscv_vector_ctx_load(const struct riscv_vector_ctx *in); + +/* Reads the live context into @out */ +void riscv_vector_ctx_store(struct riscv_vector_ctx *out); + +/* + * Installs @in, calls fn(arg), reads the result back into @out and returns + * what fn returned. Any vector register may be clobbered by the call, so + * this only says something when the caller knows what fn does. + */ +unsigned long riscv_vector_ctx_roundtrip(const struct riscv_vector_ctx *in, + struct riscv_vector_ctx *out, + unsigned long (*fn)(void *), + void *arg); + +/* + * Installs @in, issues OP-TEE syscall @scn with the single argument @arg + * through a bare ecall, reads the result back into @out and returns what + * the syscall returned. Nothing the compiler generated runs in between, so + * every vector register has to come back exactly as it went in. TA only. + */ +unsigned long riscv_vector_ctx_syscall(const struct riscv_vector_ctx *in, + struct riscv_vector_ctx *out, + unsigned long scn, unsigned long arg); + +/* + * Fills @ctx with a byte pattern that is distinct per register and derived + * from @seed, and a vl, vtype and vcsr that differ from the reset values so + * that a save or restore which drops the CSRs is caught too. + */ +static inline void riscv_vector_ctx_pattern(struct riscv_vector_ctx *ctx, + uint32_t seed, unsigned long vlenb) +{ + size_t reg = 0; + size_t n = 0; + + for (reg = 0; reg < RISCV_VECTOR_CTX_NUM_REGS; reg++) + for (n = 0; n < vlenb; n++) + ctx->vregs[reg * vlenb + n] = + (uint8_t)(seed + reg * 7 + n); + + /* + * SEW=8, LMUL=1, tail and mask agnostic, which is what a vsetvli + * of e8, m1, ta, ma produces, with vl set to one register's worth + * of elements. + */ + ctx->vtype = (1UL << 7) | (1UL << 6); + ctx->vl = vlenb; + ctx->vcsr = 0x7; + ctx->vstart = 0; +} + +/* + * Returns the index of the first vector register whose first @vlenb bytes + * differ, RISCV_VECTOR_CTX_NUM_REGS for a CSR mismatch, and -1 if the two + * contexts agree. + */ +static inline int riscv_vector_ctx_diff(const struct riscv_vector_ctx *a, + const struct riscv_vector_ctx *b, + unsigned long vlenb) +{ + size_t reg = 0; + size_t n = 0; + + for (reg = 0; reg < RISCV_VECTOR_CTX_NUM_REGS; reg++) + for (n = 0; n < vlenb; n++) + if (a->vregs[reg * vlenb + n] != + b->vregs[reg * vlenb + n]) + return (int)reg; + + if (a->vl != b->vl || a->vtype != b->vtype || a->vcsr != b->vcsr) + return RISCV_VECTOR_CTX_NUM_REGS; + + return -1; +} + +/* + * The routines above are in assembly because the whole point is to have the + * values actually sitting in the vector registers across the excursion, and + * from C the compiler would be free to keep them in memory instead, which + * would test nothing. + * + * They are emitted from this header rather than a .S file so that the same + * implementation serves the TA and xtest without either build growing an + * assembler rule. Exactly one translation unit per binary must define + * RISCV_VECTOR_CTX_IMPLEMENTATION before including this. + * + * The vector ISA is enabled per block with .option arch, so no -march + * override is needed anywhere, and comments use '#' rather than C syntax: + * these strings do not go through the preprocessor the way a .S file does, + * and Clang's integrated assembler rejects a comment in the C form. + */ +#ifdef RISCV_VECTOR_CTX_IMPLEMENTATION +_Static_assert(offsetof(struct riscv_vector_ctx, vl) == 0, + "riscv_vector_ctx layout out of sync with the assembly below"); +_Static_assert(offsetof(struct riscv_vector_ctx, vregs) == 32, + "riscv_vector_ctx layout out of sync with the assembly below"); + +__asm__( +" .text\n" +"\n" +" .globl riscv_vector_ctx_vlenb\n" +" .type riscv_vector_ctx_vlenb, @function\n" +"riscv_vector_ctx_vlenb:\n" +" csrr a0, 0xc22 # vlenb\n" +" ret\n" +" .size riscv_vector_ctx_vlenb, .-riscv_vector_ctx_vlenb\n" +"\n" +" .globl riscv_vector_ctx_load\n" +" .type riscv_vector_ctx_load, @function\n" +"riscv_vector_ctx_load:\n" +" .option push\n" +" .option arch, +v\n" +" # The whole-register loads honour vstart, so start them from zero\n" +" csrw 0x008, zero # vstart\n" +" csrr t2, 0xc22 # vlenb\n" +" slli t2, t2, 3 # eight registers at a time\n" +" addi t1, a0, 32 # ctx->vregs\n" +" vl8r.v v0, (t1)\n" +" add t1, t1, t2\n" +" vl8r.v v8, (t1)\n" +" add t1, t1, t2\n" +" vl8r.v v16, (t1)\n" +" add t1, t1, t2\n" +" vl8r.v v24, (t1)\n" +" # vl and vtype are read only, put back by re-running their vsetvl\n" +" ld t0, 0(a0) # ctx->vl\n" +" ld t3, 8(a0) # ctx->vtype\n" +" vsetvl zero, t0, t3\n" +" ld t0, 16(a0) # ctx->vcsr\n" +" csrw 0x00f, t0 # vcsr\n" +" ld t0, 24(a0) # ctx->vstart\n" +" csrw 0x008, t0 # vstart\n" +" .option pop\n" +" ret\n" +" .size riscv_vector_ctx_load, .-riscv_vector_ctx_load\n" +"\n" +" .globl riscv_vector_ctx_store\n" +" .type riscv_vector_ctx_store, @function\n" +"riscv_vector_ctx_store:\n" +" .option push\n" +" .option arch, +v\n" +" # Take the CSRs before vstart is cleared for the transfer\n" +" csrr t0, 0xc20 # vl\n" +" sd t0, 0(a0)\n" +" csrr t0, 0xc21 # vtype\n" +" sd t0, 8(a0)\n" +" csrr t0, 0x00f # vcsr\n" +" sd t0, 16(a0)\n" +" csrr t0, 0x008 # vstart\n" +" sd t0, 24(a0)\n" +" csrw 0x008, zero # vstart\n" +" csrr t2, 0xc22 # vlenb\n" +" slli t2, t2, 3\n" +" addi t1, a0, 32 # ctx->vregs\n" +" vs8r.v v0, (t1)\n" +" add t1, t1, t2\n" +" vs8r.v v8, (t1)\n" +" add t1, t1, t2\n" +" vs8r.v v16, (t1)\n" +" add t1, t1, t2\n" +" vs8r.v v24, (t1)\n" +" .option pop\n" +" ret\n" +" .size riscv_vector_ctx_store, .-riscv_vector_ctx_store\n" +"\n" +" .globl riscv_vector_ctx_roundtrip\n" +" .type riscv_vector_ctx_roundtrip, @function\n" +"riscv_vector_ctx_roundtrip:\n" +" addi sp, sp, -32\n" +" sd ra, 0(sp)\n" +" sd s0, 8(sp)\n" +" sd s1, 16(sp)\n" +" mv s0, a1 # out\n" +" mv s1, a2 # fn\n" +" call riscv_vector_ctx_load\n" +" mv a0, a3 # arg\n" +" jalr s1\n" +" mv s1, a0 # fn return value\n" +" mv a0, s0\n" +" call riscv_vector_ctx_store\n" +" mv a0, s1\n" +" ld ra, 0(sp)\n" +" ld s0, 8(sp)\n" +" ld s1, 16(sp)\n" +" addi sp, sp, 32\n" +" ret\n" +" .size riscv_vector_ctx_roundtrip, .-riscv_vector_ctx_roundtrip\n" +"\n" +" .globl riscv_vector_ctx_syscall\n" +" .type riscv_vector_ctx_syscall, @function\n" +"riscv_vector_ctx_syscall:\n" +" addi sp, sp, -32\n" +" sd ra, 0(sp)\n" +" sd s0, 8(sp)\n" +" sd s1, 16(sp)\n" +" sd s2, 24(sp)\n" +" mv s0, a1 # out\n" +" mv s1, a2 # syscall number\n" +" mv s2, a3 # syscall argument\n" +" call riscv_vector_ctx_load\n" +" # The OP-TEE syscall ABI: t0 holds the number, t1 the argument count\n" +" mv a0, s2\n" +" mv t0, s1\n" +" li t1, 1\n" +" ecall\n" +" mv s1, a0 # syscall return value\n" +" mv a0, s0\n" +" call riscv_vector_ctx_store\n" +" mv a0, s1\n" +" ld ra, 0(sp)\n" +" ld s0, 8(sp)\n" +" ld s1, 16(sp)\n" +" ld s2, 24(sp)\n" +" addi sp, sp, 32\n" +" ret\n" +" .size riscv_vector_ctx_syscall, .-riscv_vector_ctx_syscall\n" +); +#endif /* RISCV_VECTOR_CTX_IMPLEMENTATION */ + +#endif /* __riscv && __riscv_v */ +#endif /* RISCV_VECTOR_CTX_H */ diff --git a/ta/os_test/include/ta_os_test.h b/ta/os_test/include/ta_os_test.h index a6b71a699..b691c1f74 100644 --- a/ta/os_test/include/ta_os_test.h +++ b/ta/os_test/include/ta_os_test.h @@ -52,5 +52,25 @@ #define TA_OS_TEST_CMD_ASAN_MALLOC 40 #define TA_OS_TEST_CMD_ASAN_UAF 41 #define TA_OS_TEST_CMD_ASAN_MEMFUNC 42 +#define TA_OS_TEST_CMD_RISCV_VEC_CONTEXT 44 + +/* + * Sub-tests of TA_OS_TEST_CMD_RISCV_VEC_CONTEXT, selected with + * params[0].value.a. params[0].value.b carries a seed which picks the + * register pattern. On failure params[1].value.a holds the index of the + * first vector register that did not survive, or 32 for the vector CSRs, + * and params[1].value.b holds vlenb. + */ + +/* Return immediately without touching the vector unit */ +#define TA_RISCV_VEC_SUBTEST_NO_VECTOR 0 +/* Check the context survives a syscall issued through a bare ecall */ +#define TA_RISCV_VEC_SUBTEST_SYSCALL 1 +/* Read a vector CSR before any vector instruction, then check the file */ +#define TA_RISCV_VEC_SUBTEST_CSR_FIRST 2 +/* Leave the pattern in the registers and return */ +#define TA_RISCV_VEC_SUBTEST_TAINT 3 +/* Fail if the registers still hold the pattern left by an earlier TA */ +#define TA_RISCV_VEC_SUBTEST_CHECK_TAINT 4 #endif /*TA_OS_TEST_H */ diff --git a/ta/os_test/os_test.c b/ta/os_test/os_test.c index 83cf692e8..268d72f7d 100644 --- a/ta/os_test/os_test.c +++ b/ta/os_test/os_test.c @@ -16,6 +16,10 @@ #include #include +#define RISCV_VECTOR_CTX_IMPLEMENTATION +#include +#include + #include "os_test.h" #include "test_float_subj.h" #include "os_test_lib.h" @@ -1721,3 +1725,214 @@ TEE_Result ta_entry_asan_uaf(void) return TEE_ERROR_NOT_SUPPORTED; } #endif +#ifdef RISCV_VECTOR_CTX_SUPPORTED +/* + * Vector context switching. + * + * A TA runs with the vector unit disabled and is given it on the first + * vector instruction, or the first access to a vector CSR, that it + * executes. Every sub-test below therefore starts by taking that trap. + * + * The RISC-V vector calling convention has no callee-saved vector + * registers, so nothing may be assumed about v0..v31 across an ordinary + * call. The register checks here go through riscv_vector_ctx_syscall(), + * which reaches the TEE with a bare ecall and no compiler-generated code + * between installing the context and reading it back, so the whole file + * can be checked rather than a callee-saved subset. + */ + +static TEE_Result vec_check_syscall(uint32_t seed, TEE_Param params[4]) +{ + struct riscv_vector_ctx *expect = NULL; + struct riscv_vector_ctx *got = NULL; + TEE_Result res = TEE_ERROR_GENERIC; + unsigned long vlenb = 0; + unsigned long rc = 0; + int diff = 0; + + vlenb = riscv_vector_ctx_vlenb(); + params[1].value.b = vlenb; + if (vlenb > RISCV_VECTOR_CTX_VLENB_MAX) { + EMSG("Vector context: vlenb %lu is wider than this test " + "was built for", vlenb); + return TEE_ERROR_NOT_SUPPORTED; + } + + expect = TEE_Malloc(sizeof(*expect), TEE_MALLOC_FILL_ZERO); + got = TEE_Malloc(sizeof(*got), TEE_MALLOC_FILL_ZERO); + if (!expect || !got) { + res = TEE_ERROR_OUT_OF_MEMORY; + goto out; + } + + riscv_vector_ctx_pattern(expect, seed, vlenb); + + /* + * TEE_SCN_WAIT leaves the TEE altogether: the thread is suspended, + * the normal world runs, and the thread is later resumed through + * thread_resume_from_rpc(). The TA context has to survive that. + */ + rc = riscv_vector_ctx_syscall(expect, got, TEE_SCN_WAIT, 10); + if (rc != TEE_SUCCESS) { + EMSG("Vector context: syscall failed: %#lx", rc); + res = (TEE_Result)rc; + goto out; + } + + diff = riscv_vector_ctx_diff(expect, got, vlenb); + if (diff < 0) { + res = TEE_SUCCESS; + goto out; + } + + params[1].value.a = diff; + if (diff == RISCV_VECTOR_CTX_NUM_REGS) + EMSG("Vector context: CSRs changed, vl %lu/%lu vtype %#lx/%#lx" + " vcsr %#lx/%#lx", expect->vl, got->vl, expect->vtype, + got->vtype, expect->vcsr, got->vcsr); + else + EMSG("Vector context: v%d was the first register lost", diff); + res = TEE_ERROR_GENERIC; +out: + TEE_Free(expect); + TEE_Free(got); + + return res; +} + +static TEE_Result vec_check_csr_first(TEE_Param params[4]) +{ + unsigned long vlenb = 0; + + /* + * Reading vlenb touches the vector unit without issuing a vector + * instruction. With VS Off that traps as an illegal instruction just + * as an instruction would, so getting an answer at all is the check: + * a TEE that decodes only the instructions and not the CSRs kills + * the TA here instead of handing it a context. + * + * Nothing is asserted about the register file. The vector calling + * convention has no callee-saved vector registers, and this TA is + * compiled for a hart with the extension, so the compiler is free to + * use v0..v31 in ordinary C between the trap and any read we could + * make. What the registers must not contain is checked against a + * known pattern by the taint sub-test instead. + */ + vlenb = riscv_vector_ctx_vlenb(); + params[1].value.b = vlenb; + + if (!vlenb || vlenb > RISCV_VECTOR_CTX_VLENB_MAX || + (vlenb & (vlenb - 1))) { + EMSG("Vector context: implausible vlenb %lu", vlenb); + return TEE_ERROR_GENERIC; + } + + return TEE_SUCCESS; +} + +static TEE_Result vec_check_taint(uint32_t seed, TEE_Param params[4]) +{ + struct riscv_vector_ctx *taint = NULL; + struct riscv_vector_ctx *got = NULL; + TEE_Result res = TEE_SUCCESS; + unsigned long vlenb = 0; + size_t reg = 0; + + vlenb = riscv_vector_ctx_vlenb(); + params[1].value.b = vlenb; + if (!vlenb || vlenb > RISCV_VECTOR_CTX_VLENB_MAX) + return TEE_ERROR_NOT_SUPPORTED; + + taint = TEE_Malloc(sizeof(*taint), TEE_MALLOC_FILL_ZERO); + got = TEE_Malloc(sizeof(*got), TEE_MALLOC_FILL_ZERO); + if (!taint || !got) { + res = TEE_ERROR_OUT_OF_MEMORY; + goto out; + } + + riscv_vector_ctx_store(got); + riscv_vector_ctx_pattern(taint, seed, vlenb); + + for (reg = 0; reg < RISCV_VECTOR_CTX_NUM_REGS; reg++) { + if (!memcmp(got->vregs + reg * vlenb, + taint->vregs + reg * vlenb, vlenb)) { + EMSG("Vector context: v%zu still holds what an " + "earlier TA left there", reg); + params[1].value.a = reg; + res = TEE_ERROR_GENERIC; + break; + } + } +out: + TEE_Free(taint); + TEE_Free(got); + + return res; +} + +TEE_Result ta_entry_riscv_vec_context(uint32_t param_types, + TEE_Param params[4]) +{ + uint32_t subtest = 0; + uint32_t seed = 0; + + if (param_types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_INPUT, + TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE)) + return TEE_ERROR_BAD_PARAMETERS; + + subtest = params[0].value.a; + seed = params[0].value.b; + params[1].value.a = 0; + params[1].value.b = 0; + + switch (subtest) { + case TA_RISCV_VEC_SUBTEST_NO_VECTOR: + /* + * Used by the normal world to check that its own context + * survives a call into a TA which never enables the vector + * unit, the path where the TEE elides the restore on the + * way out. + */ + return TEE_SUCCESS; + + case TA_RISCV_VEC_SUBTEST_SYSCALL: + return vec_check_syscall(seed, params); + + case TA_RISCV_VEC_SUBTEST_CSR_FIRST: + return vec_check_csr_first(params); + + case TA_RISCV_VEC_SUBTEST_TAINT: { + struct riscv_vector_ctx *taint = NULL; + unsigned long vlenb = riscv_vector_ctx_vlenb(); + + params[1].value.b = vlenb; + if (!vlenb || vlenb > RISCV_VECTOR_CTX_VLENB_MAX) + return TEE_ERROR_NOT_SUPPORTED; + + taint = TEE_Malloc(sizeof(*taint), TEE_MALLOC_FILL_ZERO); + if (!taint) + return TEE_ERROR_OUT_OF_MEMORY; + + riscv_vector_ctx_pattern(taint, seed, vlenb); + riscv_vector_ctx_load(taint); + TEE_Free(taint); + + return TEE_SUCCESS; + } + + case TA_RISCV_VEC_SUBTEST_CHECK_TAINT: + return vec_check_taint(seed, params); + + default: + return TEE_ERROR_BAD_PARAMETERS; + } +} +#else /*RISCV_VECTOR_CTX_SUPPORTED*/ +TEE_Result ta_entry_riscv_vec_context(uint32_t param_types __unused, + TEE_Param params[4] __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} +#endif /*RISCV_VECTOR_CTX_SUPPORTED*/ diff --git a/ta/os_test/ta_entry.c b/ta/os_test/ta_entry.c index 069e39ac8..cf7ff422c 100644 --- a/ta/os_test/ta_entry.c +++ b/ta/os_test/ta_entry.c @@ -191,6 +191,9 @@ TEE_Result TA_InvokeCommandEntryPoint(void *pSessionContext, case TA_OS_TEST_CMD_ASAN_MEMFUNC: return ta_entry_asan_memfunc(); + case TA_OS_TEST_CMD_RISCV_VEC_CONTEXT: + return ta_entry_riscv_vec_context(nParamTypes, pParams); + default: return TEE_ERROR_BAD_PARAMETERS; }