1c40a6505SJens Wiklander // SPDX-License-Identifier: BSD-2-Clause
2c40a6505SJens Wiklander /*
3c40a6505SJens Wiklander * Copyright (c) 2014, STMicroelectronics International N.V.
4ef142203SJens Wiklander * Copyright (c) 2015-2020, 2022 Linaro Limited
5c40a6505SJens Wiklander */
6c40a6505SJens Wiklander
7c40a6505SJens Wiklander #include <initcall.h>
8c40a6505SJens Wiklander #include <kernel/linker.h>
9c40a6505SJens Wiklander #include <kernel/user_access.h>
10c185655eSJelle Sels #include <kernel/user_mode_ctx.h>
11a0e8ffe9SJens Wiklander #include <memtag.h>
1289c9728dSJens Wiklander #include <mm/vm.h>
13c40a6505SJens Wiklander #include <string.h>
14c40a6505SJens Wiklander #include <tee_api_types.h>
15c40a6505SJens Wiklander #include <types_ext.h>
16c40a6505SJens Wiklander
17c5a0db99SJens Wiklander #define BB_ALIGNMENT (sizeof(long) * 2)
18c5a0db99SJens Wiklander
get_current_uctx(void)19c5a0db99SJens Wiklander static struct user_mode_ctx *get_current_uctx(void)
20c40a6505SJens Wiklander {
2100b3b9a2SJens Wiklander struct ts_session *s = ts_get_current_session();
227e4100f3SJens Wiklander
23c5a0db99SJens Wiklander if (!is_user_mode_ctx(s->ctx)) {
24c5a0db99SJens Wiklander /*
25c5a0db99SJens Wiklander * We may be called within a PTA session, which doesn't
26c5a0db99SJens Wiklander * have a user_mode_ctx. Here, try to retrieve the
27c5a0db99SJens Wiklander * user_mode_ctx associated with the calling session.
28c5a0db99SJens Wiklander */
29c5a0db99SJens Wiklander s = TAILQ_NEXT(s, link_tsd);
30c5a0db99SJens Wiklander if (!s || !is_user_mode_ctx(s->ctx))
31c5a0db99SJens Wiklander return NULL;
32c5a0db99SJens Wiklander }
33c5a0db99SJens Wiklander
34c5a0db99SJens Wiklander return to_user_mode_ctx(s->ctx);
35c5a0db99SJens Wiklander }
36c5a0db99SJens Wiklander
check_user_access(uint32_t flags,const void * uaddr,size_t len)37e59bc1dbSJens Wiklander TEE_Result check_user_access(uint32_t flags, const void *uaddr, size_t len)
38c5a0db99SJens Wiklander {
39c5a0db99SJens Wiklander struct user_mode_ctx *uctx = get_current_uctx();
40c5a0db99SJens Wiklander
41c5a0db99SJens Wiklander if (!uctx)
42c5a0db99SJens Wiklander return TEE_ERROR_GENERIC;
43c5a0db99SJens Wiklander
44c5a0db99SJens Wiklander return vm_check_access_rights(uctx, flags, (vaddr_t)uaddr, len);
45c40a6505SJens Wiklander }
46c40a6505SJens Wiklander
copy_from_user(void * kaddr,const void * uaddr,size_t len)47c40a6505SJens Wiklander TEE_Result copy_from_user(void *kaddr, const void *uaddr, size_t len)
48c40a6505SJens Wiklander {
497e4100f3SJens Wiklander uint32_t flags = TEE_MEMORY_ACCESS_READ | TEE_MEMORY_ACCESS_ANY_OWNER;
50ef142203SJens Wiklander TEE_Result res = TEE_SUCCESS;
51c40a6505SJens Wiklander
52ef142203SJens Wiklander uaddr = memtag_strip_tag_const(uaddr);
53e59bc1dbSJens Wiklander res = check_user_access(flags, uaddr, len);
54*df46e553SAbhishek Revadekar if (!res && kaddr && uaddr) {
554e154320SSeonghyun Park enter_user_access();
56c40a6505SJens Wiklander memcpy(kaddr, uaddr, len);
574e154320SSeonghyun Park exit_user_access();
584e154320SSeonghyun Park }
597e4100f3SJens Wiklander
607e4100f3SJens Wiklander return res;
61c40a6505SJens Wiklander }
62c40a6505SJens Wiklander
copy_to_user(void * uaddr,const void * kaddr,size_t len)63c40a6505SJens Wiklander TEE_Result copy_to_user(void *uaddr, const void *kaddr, size_t len)
64c40a6505SJens Wiklander {
657e4100f3SJens Wiklander uint32_t flags = TEE_MEMORY_ACCESS_WRITE | TEE_MEMORY_ACCESS_ANY_OWNER;
66ef142203SJens Wiklander TEE_Result res = TEE_SUCCESS;
67c40a6505SJens Wiklander
68ef142203SJens Wiklander uaddr = memtag_strip_tag(uaddr);
69e59bc1dbSJens Wiklander res = check_user_access(flags, uaddr, len);
70*df46e553SAbhishek Revadekar if (!res && kaddr && uaddr) {
714e154320SSeonghyun Park enter_user_access();
72c40a6505SJens Wiklander memcpy(uaddr, kaddr, len);
734e154320SSeonghyun Park exit_user_access();
744e154320SSeonghyun Park }
757e4100f3SJens Wiklander
767e4100f3SJens Wiklander return res;
777e4100f3SJens Wiklander }
787e4100f3SJens Wiklander
copy_from_user_private(void * kaddr,const void * uaddr,size_t len)797e4100f3SJens Wiklander TEE_Result copy_from_user_private(void *kaddr, const void *uaddr, size_t len)
807e4100f3SJens Wiklander {
817e4100f3SJens Wiklander uint32_t flags = TEE_MEMORY_ACCESS_READ;
82ef142203SJens Wiklander TEE_Result res = TEE_SUCCESS;
837e4100f3SJens Wiklander
84ef142203SJens Wiklander uaddr = memtag_strip_tag_const(uaddr);
85e59bc1dbSJens Wiklander res = check_user_access(flags, uaddr, len);
86*df46e553SAbhishek Revadekar if (!res && kaddr && uaddr) {
874e154320SSeonghyun Park enter_user_access();
887e4100f3SJens Wiklander memcpy(kaddr, uaddr, len);
894e154320SSeonghyun Park exit_user_access();
904e154320SSeonghyun Park }
917e4100f3SJens Wiklander
927e4100f3SJens Wiklander return res;
937e4100f3SJens Wiklander }
947e4100f3SJens Wiklander
copy_to_user_private(void * uaddr,const void * kaddr,size_t len)957e4100f3SJens Wiklander TEE_Result copy_to_user_private(void *uaddr, const void *kaddr, size_t len)
967e4100f3SJens Wiklander {
977e4100f3SJens Wiklander uint32_t flags = TEE_MEMORY_ACCESS_WRITE;
98ef142203SJens Wiklander TEE_Result res = TEE_SUCCESS;
997e4100f3SJens Wiklander
100ef142203SJens Wiklander uaddr = memtag_strip_tag(uaddr);
101e59bc1dbSJens Wiklander res = check_user_access(flags, uaddr, len);
102*df46e553SAbhishek Revadekar if (!res && kaddr && uaddr) {
1034e154320SSeonghyun Park enter_user_access();
1047e4100f3SJens Wiklander memcpy(uaddr, kaddr, len);
1054e154320SSeonghyun Park exit_user_access();
1064e154320SSeonghyun Park }
1077e4100f3SJens Wiklander
1087e4100f3SJens Wiklander return res;
109c40a6505SJens Wiklander }
110c40a6505SJens Wiklander
maybe_tag_bb(void * buf,size_t sz)111e980c685SJens Wiklander static void *maybe_tag_bb(void *buf, size_t sz)
112e980c685SJens Wiklander {
113e980c685SJens Wiklander static_assert(MEMTAG_GRANULE_SIZE <= BB_ALIGNMENT);
114e980c685SJens Wiklander
115e980c685SJens Wiklander if (!MEMTAG_IS_ENABLED)
116e980c685SJens Wiklander return buf;
117e980c685SJens Wiklander
118e980c685SJens Wiklander assert(!((vaddr_t)buf % MEMTAG_GRANULE_SIZE));
119e980c685SJens Wiklander return memtag_set_random_tags(buf, ROUNDUP(sz, MEMTAG_GRANULE_SIZE));
120e980c685SJens Wiklander }
121e980c685SJens Wiklander
maybe_untag_bb(void * buf,size_t sz)122e980c685SJens Wiklander static void maybe_untag_bb(void *buf, size_t sz)
123e980c685SJens Wiklander {
124e980c685SJens Wiklander if (MEMTAG_IS_ENABLED) {
125e980c685SJens Wiklander assert(!((vaddr_t)buf % MEMTAG_GRANULE_SIZE));
126e980c685SJens Wiklander memtag_set_tags(buf, ROUNDUP(sz, MEMTAG_GRANULE_SIZE), 0);
127e980c685SJens Wiklander }
128e980c685SJens Wiklander }
129e980c685SJens Wiklander
bb_alloc(size_t len)130c5a0db99SJens Wiklander void *bb_alloc(size_t len)
131c5a0db99SJens Wiklander {
132c5a0db99SJens Wiklander struct user_mode_ctx *uctx = get_current_uctx();
133c5a0db99SJens Wiklander size_t offs = 0;
134c5a0db99SJens Wiklander void *bb = NULL;
135c5a0db99SJens Wiklander
136c5a0db99SJens Wiklander if (uctx && !ADD_OVERFLOW(uctx->bbuf_offs, len, &offs) &&
137c5a0db99SJens Wiklander offs <= uctx->bbuf_size) {
138e980c685SJens Wiklander bb = maybe_tag_bb(uctx->bbuf + uctx->bbuf_offs, len);
139c5a0db99SJens Wiklander uctx->bbuf_offs = ROUNDUP(offs, BB_ALIGNMENT);
140c5a0db99SJens Wiklander }
141c5a0db99SJens Wiklander return bb;
142c5a0db99SJens Wiklander }
143c5a0db99SJens Wiklander
bb_free_helper(struct user_mode_ctx * uctx,vaddr_t bb,size_t len)144c5a0db99SJens Wiklander static void bb_free_helper(struct user_mode_ctx *uctx, vaddr_t bb, size_t len)
145c5a0db99SJens Wiklander {
146c5a0db99SJens Wiklander vaddr_t bbuf = (vaddr_t)uctx->bbuf;
147c5a0db99SJens Wiklander
148c5a0db99SJens Wiklander if (bb >= bbuf && IS_ALIGNED(bb, BB_ALIGNMENT)) {
149c5a0db99SJens Wiklander size_t prev_offs = bb - bbuf;
150c5a0db99SJens Wiklander
151e980c685SJens Wiklander /*
152e980c685SJens Wiklander * Even if we can't update offset we can still invalidate
153e980c685SJens Wiklander * the memory allocation.
154e980c685SJens Wiklander */
155e980c685SJens Wiklander maybe_untag_bb((void *)bb, len);
156e980c685SJens Wiklander
157c5a0db99SJens Wiklander if (prev_offs + ROUNDUP(len, BB_ALIGNMENT) == uctx->bbuf_offs)
158c5a0db99SJens Wiklander uctx->bbuf_offs = prev_offs;
159c5a0db99SJens Wiklander }
160c5a0db99SJens Wiklander }
161c5a0db99SJens Wiklander
bb_free(void * bb,size_t len)162c5a0db99SJens Wiklander void bb_free(void *bb, size_t len)
163c5a0db99SJens Wiklander {
164c5a0db99SJens Wiklander struct user_mode_ctx *uctx = get_current_uctx();
165c5a0db99SJens Wiklander
166c5a0db99SJens Wiklander if (uctx)
167e980c685SJens Wiklander bb_free_helper(uctx, memtag_strip_tag_vaddr(bb), len);
168c5a0db99SJens Wiklander }
169c5a0db99SJens Wiklander
bb_free_wipe(void * bb,size_t len)170b39fcd95SJens Wiklander void bb_free_wipe(void *bb, size_t len)
171b39fcd95SJens Wiklander {
172b39fcd95SJens Wiklander if (bb)
173b39fcd95SJens Wiklander memset(bb, 0, len);
174b39fcd95SJens Wiklander bb_free(bb, len);
175b39fcd95SJens Wiklander }
176b39fcd95SJens Wiklander
bb_reset(void)177c5a0db99SJens Wiklander void bb_reset(void)
178c5a0db99SJens Wiklander {
179c5a0db99SJens Wiklander struct user_mode_ctx *uctx = get_current_uctx();
180c5a0db99SJens Wiklander
181e980c685SJens Wiklander if (uctx) {
182e980c685SJens Wiklander /*
183e980c685SJens Wiklander * Only the part up to the offset have been allocated, so
184e980c685SJens Wiklander * no need to clear tags beyond that.
185e980c685SJens Wiklander */
186e980c685SJens Wiklander maybe_untag_bb(uctx->bbuf, uctx->bbuf_offs);
187e980c685SJens Wiklander
188c5a0db99SJens Wiklander uctx->bbuf_offs = 0;
189c5a0db99SJens Wiklander }
190e980c685SJens Wiklander }
191c5a0db99SJens Wiklander
clear_user(void * uaddr,size_t n)192e5aa0f8cSSeonghyun Park TEE_Result clear_user(void *uaddr, size_t n)
193e5aa0f8cSSeonghyun Park {
194e5aa0f8cSSeonghyun Park uint32_t flags = TEE_MEMORY_ACCESS_WRITE | TEE_MEMORY_ACCESS_ANY_OWNER;
195e5aa0f8cSSeonghyun Park TEE_Result res = TEE_SUCCESS;
196e5aa0f8cSSeonghyun Park
197e5aa0f8cSSeonghyun Park uaddr = memtag_strip_tag(uaddr);
198e59bc1dbSJens Wiklander res = check_user_access(flags, uaddr, n);
199e5aa0f8cSSeonghyun Park if (res)
200e5aa0f8cSSeonghyun Park return res;
201e5aa0f8cSSeonghyun Park
2024e154320SSeonghyun Park enter_user_access();
203e5aa0f8cSSeonghyun Park memset(uaddr, 0, n);
2044e154320SSeonghyun Park exit_user_access();
205e5aa0f8cSSeonghyun Park
206e5aa0f8cSSeonghyun Park return TEE_SUCCESS;
207e5aa0f8cSSeonghyun Park }
208e5aa0f8cSSeonghyun Park
strnlen_user(const void * uaddr,size_t len)209e5aa0f8cSSeonghyun Park size_t strnlen_user(const void *uaddr, size_t len)
210e5aa0f8cSSeonghyun Park {
211e5aa0f8cSSeonghyun Park uint32_t flags = TEE_MEMORY_ACCESS_READ | TEE_MEMORY_ACCESS_ANY_OWNER;
212e5aa0f8cSSeonghyun Park TEE_Result res = TEE_SUCCESS;
213e5aa0f8cSSeonghyun Park size_t n = 0;
214e5aa0f8cSSeonghyun Park
2150bba39ebSJens Wiklander if (!len)
2160bba39ebSJens Wiklander return 0;
2170bba39ebSJens Wiklander
218e5aa0f8cSSeonghyun Park uaddr = memtag_strip_tag_const(uaddr);
219e59bc1dbSJens Wiklander res = check_user_access(flags, uaddr, len);
2204e154320SSeonghyun Park if (!res) {
2214e154320SSeonghyun Park enter_user_access();
222e5aa0f8cSSeonghyun Park n = strnlen(uaddr, len);
2234e154320SSeonghyun Park exit_user_access();
2244e154320SSeonghyun Park }
225e5aa0f8cSSeonghyun Park
226e5aa0f8cSSeonghyun Park return n;
227e5aa0f8cSSeonghyun Park }
228e5aa0f8cSSeonghyun Park
__bb_memdup_user(TEE_Result (* copy_func)(void * uaddr,const void * kaddr,size_t len),const void * src,size_t len,void ** p)2290bba39ebSJens Wiklander static TEE_Result __bb_memdup_user(TEE_Result (*copy_func)(void *uaddr,
2300bba39ebSJens Wiklander const void *kaddr,
2310bba39ebSJens Wiklander size_t len),
2320bba39ebSJens Wiklander const void *src, size_t len, void **p)
233e5aa0f8cSSeonghyun Park {
234e5aa0f8cSSeonghyun Park TEE_Result res = TEE_SUCCESS;
235e5aa0f8cSSeonghyun Park void *buf = NULL;
236e5aa0f8cSSeonghyun Park
237e5aa0f8cSSeonghyun Park buf = bb_alloc(len);
238e5aa0f8cSSeonghyun Park if (!buf)
239e5aa0f8cSSeonghyun Park return TEE_ERROR_OUT_OF_MEMORY;
240e5aa0f8cSSeonghyun Park
2410bba39ebSJens Wiklander if (len)
2420bba39ebSJens Wiklander res = copy_func(buf, src, len);
2430bba39ebSJens Wiklander
244e5aa0f8cSSeonghyun Park if (res)
245e5aa0f8cSSeonghyun Park bb_free(buf, len);
246e5aa0f8cSSeonghyun Park else
247e5aa0f8cSSeonghyun Park *p = buf;
248e5aa0f8cSSeonghyun Park
249e5aa0f8cSSeonghyun Park return res;
250e5aa0f8cSSeonghyun Park }
251e5aa0f8cSSeonghyun Park
bb_memdup_user(const void * src,size_t len,void ** p)2520bba39ebSJens Wiklander TEE_Result bb_memdup_user(const void *src, size_t len, void **p)
2530bba39ebSJens Wiklander {
2540bba39ebSJens Wiklander return __bb_memdup_user(copy_from_user, src, len, p);
2550bba39ebSJens Wiklander }
2560bba39ebSJens Wiklander
bb_memdup_user_private(const void * src,size_t len,void ** p)257e5aa0f8cSSeonghyun Park TEE_Result bb_memdup_user_private(const void *src, size_t len, void **p)
258e5aa0f8cSSeonghyun Park {
2590bba39ebSJens Wiklander return __bb_memdup_user(copy_from_user_private, src, len, p);
260e5aa0f8cSSeonghyun Park }
261e5aa0f8cSSeonghyun Park
bb_strndup_user(const char * src,size_t maxlen,char ** dst,size_t * dstlen)2629c99bb1dSJens Wiklander TEE_Result bb_strndup_user(const char *src, size_t maxlen, char **dst,
2639c99bb1dSJens Wiklander size_t *dstlen)
2649c99bb1dSJens Wiklander {
2659c99bb1dSJens Wiklander uint32_t flags = TEE_MEMORY_ACCESS_READ | TEE_MEMORY_ACCESS_ANY_OWNER;
2669c99bb1dSJens Wiklander TEE_Result res = TEE_SUCCESS;
2679c99bb1dSJens Wiklander size_t l = 0;
2689c99bb1dSJens Wiklander char *d = NULL;
2699c99bb1dSJens Wiklander
2709c99bb1dSJens Wiklander src = memtag_strip_tag_const(src);
2710bba39ebSJens Wiklander if (maxlen) {
272e59bc1dbSJens Wiklander res = check_user_access(flags, src, maxlen);
2739c99bb1dSJens Wiklander if (res)
2749c99bb1dSJens Wiklander return res;
2754e154320SSeonghyun Park
2764e154320SSeonghyun Park enter_user_access();
2779c99bb1dSJens Wiklander l = strnlen(src, maxlen);
2784e154320SSeonghyun Park exit_user_access();
2790bba39ebSJens Wiklander }
2804e154320SSeonghyun Park
2819c99bb1dSJens Wiklander d = bb_alloc(l + 1);
2829c99bb1dSJens Wiklander if (!d)
2839c99bb1dSJens Wiklander return TEE_ERROR_OUT_OF_MEMORY;
2844e154320SSeonghyun Park
285*df46e553SAbhishek Revadekar if (l && src && d) {
2864e154320SSeonghyun Park enter_user_access();
2879c99bb1dSJens Wiklander memcpy(d, src, l);
2884e154320SSeonghyun Park exit_user_access();
2890bba39ebSJens Wiklander }
2904e154320SSeonghyun Park
2919c99bb1dSJens Wiklander d[l] = 0;
2929c99bb1dSJens Wiklander
2939c99bb1dSJens Wiklander *dst = d;
2949c99bb1dSJens Wiklander *dstlen = l;
2959c99bb1dSJens Wiklander return TEE_SUCCESS;
2969c99bb1dSJens Wiklander }
2979c99bb1dSJens Wiklander
copy_kaddr_to_uref(uint32_t * uref,void * kaddr)298c40a6505SJens Wiklander TEE_Result copy_kaddr_to_uref(uint32_t *uref, void *kaddr)
299c40a6505SJens Wiklander {
300c40a6505SJens Wiklander uint32_t ref = kaddr_to_uref(kaddr);
301c40a6505SJens Wiklander
3027e4100f3SJens Wiklander return copy_to_user_private(uref, &ref, sizeof(ref));
303c40a6505SJens Wiklander }
304c40a6505SJens Wiklander
kaddr_to_uref(void * kaddr)305c40a6505SJens Wiklander uint32_t kaddr_to_uref(void *kaddr)
306c40a6505SJens Wiklander {
307a0e8ffe9SJens Wiklander if (MEMTAG_IS_ENABLED) {
308a0e8ffe9SJens Wiklander unsigned int uref_tag_shift = 32 - MEMTAG_TAG_WIDTH;
309a0e8ffe9SJens Wiklander vaddr_t uref = memtag_strip_tag_vaddr(kaddr);
310a0e8ffe9SJens Wiklander
311a0e8ffe9SJens Wiklander uref -= VCORE_START_VA;
312a0e8ffe9SJens Wiklander assert(uref < (UINT32_MAX >> MEMTAG_TAG_WIDTH));
313963eb457SClement Faure uref |= (vaddr_t)memtag_get_tag(kaddr) << uref_tag_shift;
314a0e8ffe9SJens Wiklander return uref;
315a0e8ffe9SJens Wiklander }
316a0e8ffe9SJens Wiklander
317c40a6505SJens Wiklander assert(((vaddr_t)kaddr - VCORE_START_VA) < UINT32_MAX);
318c40a6505SJens Wiklander return (vaddr_t)kaddr - VCORE_START_VA;
319c40a6505SJens Wiklander }
320c40a6505SJens Wiklander
uref_to_vaddr(uint32_t uref)321c40a6505SJens Wiklander vaddr_t uref_to_vaddr(uint32_t uref)
322c40a6505SJens Wiklander {
323a0e8ffe9SJens Wiklander if (MEMTAG_IS_ENABLED) {
324a0e8ffe9SJens Wiklander vaddr_t u = uref & (UINT32_MAX >> MEMTAG_TAG_WIDTH);
325a0e8ffe9SJens Wiklander unsigned int uref_tag_shift = 32 - MEMTAG_TAG_WIDTH;
326a0e8ffe9SJens Wiklander uint8_t tag = uref >> uref_tag_shift;
327a0e8ffe9SJens Wiklander
328a0e8ffe9SJens Wiklander return memtag_insert_tag_vaddr(VCORE_START_VA + u, tag);
329a0e8ffe9SJens Wiklander }
330a0e8ffe9SJens Wiklander
331c40a6505SJens Wiklander return VCORE_START_VA + uref;
332c40a6505SJens Wiklander }
333