xref: /optee_os/core/kernel/user_access.c (revision e980c685775456df13bbb424456aa428cac987e0)
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 
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 
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 
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);
544e154320SSeonghyun Park 	if (!res) {
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 
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);
704e154320SSeonghyun Park 	if (!res) {
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 
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);
864e154320SSeonghyun Park 	if (!res) {
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 
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);
1024e154320SSeonghyun Park 	if (!res) {
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 
111*e980c685SJens Wiklander static void *maybe_tag_bb(void *buf, size_t sz)
112*e980c685SJens Wiklander {
113*e980c685SJens Wiklander 	static_assert(MEMTAG_GRANULE_SIZE <= BB_ALIGNMENT);
114*e980c685SJens Wiklander 
115*e980c685SJens Wiklander 	if (!MEMTAG_IS_ENABLED)
116*e980c685SJens Wiklander 		return buf;
117*e980c685SJens Wiklander 
118*e980c685SJens Wiklander 	assert(!((vaddr_t)buf % MEMTAG_GRANULE_SIZE));
119*e980c685SJens Wiklander 	return memtag_set_random_tags(buf, ROUNDUP(sz, MEMTAG_GRANULE_SIZE));
120*e980c685SJens Wiklander }
121*e980c685SJens Wiklander 
122*e980c685SJens Wiklander static void maybe_untag_bb(void *buf, size_t sz)
123*e980c685SJens Wiklander {
124*e980c685SJens Wiklander 	if (MEMTAG_IS_ENABLED) {
125*e980c685SJens Wiklander 		assert(!((vaddr_t)buf % MEMTAG_GRANULE_SIZE));
126*e980c685SJens Wiklander 		memtag_set_tags(buf, ROUNDUP(sz, MEMTAG_GRANULE_SIZE), 0);
127*e980c685SJens Wiklander 	}
128*e980c685SJens Wiklander }
129*e980c685SJens Wiklander 
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) {
138*e980c685SJens 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 
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 
151*e980c685SJens Wiklander 		/*
152*e980c685SJens Wiklander 		 * Even if we can't update offset we can still invalidate
153*e980c685SJens Wiklander 		 * the memory allocation.
154*e980c685SJens Wiklander 		 */
155*e980c685SJens Wiklander 		maybe_untag_bb((void *)bb, len);
156*e980c685SJens 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 
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)
167*e980c685SJens Wiklander 		bb_free_helper(uctx, memtag_strip_tag_vaddr(bb), len);
168c5a0db99SJens Wiklander }
169c5a0db99SJens Wiklander 
170c5a0db99SJens Wiklander void bb_reset(void)
171c5a0db99SJens Wiklander {
172c5a0db99SJens Wiklander 	struct user_mode_ctx *uctx = get_current_uctx();
173c5a0db99SJens Wiklander 
174*e980c685SJens Wiklander 	if (uctx) {
175*e980c685SJens Wiklander 		/*
176*e980c685SJens Wiklander 		 * Only the part up to the offset have been allocated, so
177*e980c685SJens Wiklander 		 * no need to clear tags beyond that.
178*e980c685SJens Wiklander 		 */
179*e980c685SJens Wiklander 		maybe_untag_bb(uctx->bbuf, uctx->bbuf_offs);
180*e980c685SJens Wiklander 
181c5a0db99SJens Wiklander 		uctx->bbuf_offs = 0;
182c5a0db99SJens Wiklander 	}
183*e980c685SJens Wiklander }
184c5a0db99SJens Wiklander 
185e5aa0f8cSSeonghyun Park TEE_Result clear_user(void *uaddr, size_t n)
186e5aa0f8cSSeonghyun Park {
187e5aa0f8cSSeonghyun Park 	uint32_t flags = TEE_MEMORY_ACCESS_WRITE | TEE_MEMORY_ACCESS_ANY_OWNER;
188e5aa0f8cSSeonghyun Park 	TEE_Result res = TEE_SUCCESS;
189e5aa0f8cSSeonghyun Park 
190e5aa0f8cSSeonghyun Park 	uaddr = memtag_strip_tag(uaddr);
191e59bc1dbSJens Wiklander 	res = check_user_access(flags, uaddr, n);
192e5aa0f8cSSeonghyun Park 	if (res)
193e5aa0f8cSSeonghyun Park 		return res;
194e5aa0f8cSSeonghyun Park 
1954e154320SSeonghyun Park 	enter_user_access();
196e5aa0f8cSSeonghyun Park 	memset(uaddr, 0, n);
1974e154320SSeonghyun Park 	exit_user_access();
198e5aa0f8cSSeonghyun Park 
199e5aa0f8cSSeonghyun Park 	return TEE_SUCCESS;
200e5aa0f8cSSeonghyun Park }
201e5aa0f8cSSeonghyun Park 
202e5aa0f8cSSeonghyun Park size_t strnlen_user(const void *uaddr, size_t len)
203e5aa0f8cSSeonghyun Park {
204e5aa0f8cSSeonghyun Park 	uint32_t flags = TEE_MEMORY_ACCESS_READ | TEE_MEMORY_ACCESS_ANY_OWNER;
205e5aa0f8cSSeonghyun Park 	TEE_Result res = TEE_SUCCESS;
206e5aa0f8cSSeonghyun Park 	size_t n = 0;
207e5aa0f8cSSeonghyun Park 
208e5aa0f8cSSeonghyun Park 	uaddr = memtag_strip_tag_const(uaddr);
209e59bc1dbSJens Wiklander 	res = check_user_access(flags, uaddr, len);
2104e154320SSeonghyun Park 	if (!res) {
2114e154320SSeonghyun Park 		enter_user_access();
212e5aa0f8cSSeonghyun Park 		n = strnlen(uaddr, len);
2134e154320SSeonghyun Park 		exit_user_access();
2144e154320SSeonghyun Park 	}
215e5aa0f8cSSeonghyun Park 
216e5aa0f8cSSeonghyun Park 	return n;
217e5aa0f8cSSeonghyun Park }
218e5aa0f8cSSeonghyun Park 
219e5aa0f8cSSeonghyun Park TEE_Result bb_memdup_user(const void *src, size_t len, void **p)
220e5aa0f8cSSeonghyun Park {
221e5aa0f8cSSeonghyun Park 	TEE_Result res = TEE_SUCCESS;
222e5aa0f8cSSeonghyun Park 	void *buf = NULL;
223e5aa0f8cSSeonghyun Park 
224e5aa0f8cSSeonghyun Park 	buf = bb_alloc(len);
225e5aa0f8cSSeonghyun Park 	if (!buf)
226e5aa0f8cSSeonghyun Park 		return TEE_ERROR_OUT_OF_MEMORY;
227e5aa0f8cSSeonghyun Park 
228e5aa0f8cSSeonghyun Park 	res = copy_from_user(buf, src, len);
229e5aa0f8cSSeonghyun Park 	if (res)
230e5aa0f8cSSeonghyun Park 		bb_free(buf, len);
231e5aa0f8cSSeonghyun Park 	else
232e5aa0f8cSSeonghyun Park 		*p = buf;
233e5aa0f8cSSeonghyun Park 
234e5aa0f8cSSeonghyun Park 	return res;
235e5aa0f8cSSeonghyun Park }
236e5aa0f8cSSeonghyun Park 
237e5aa0f8cSSeonghyun Park TEE_Result bb_memdup_user_private(const void *src, size_t len, void **p)
238e5aa0f8cSSeonghyun Park {
239e5aa0f8cSSeonghyun Park 	TEE_Result res = TEE_SUCCESS;
240e5aa0f8cSSeonghyun Park 	void *buf = NULL;
241e5aa0f8cSSeonghyun Park 
242e5aa0f8cSSeonghyun Park 	buf = bb_alloc(len);
243e5aa0f8cSSeonghyun Park 	if (!buf)
244e5aa0f8cSSeonghyun Park 		return TEE_ERROR_OUT_OF_MEMORY;
245e5aa0f8cSSeonghyun Park 
246e5aa0f8cSSeonghyun Park 	res = copy_from_user_private(buf, src, len);
247e5aa0f8cSSeonghyun Park 	if (res)
248e5aa0f8cSSeonghyun Park 		bb_free(buf, len);
249e5aa0f8cSSeonghyun Park 	else
250e5aa0f8cSSeonghyun Park 		*p = buf;
251e5aa0f8cSSeonghyun Park 
252e5aa0f8cSSeonghyun Park 	return res;
253e5aa0f8cSSeonghyun Park }
254e5aa0f8cSSeonghyun Park 
2559c99bb1dSJens Wiklander TEE_Result bb_strndup_user(const char *src, size_t maxlen, char **dst,
2569c99bb1dSJens Wiklander 			   size_t *dstlen)
2579c99bb1dSJens Wiklander {
2589c99bb1dSJens Wiklander 	uint32_t flags = TEE_MEMORY_ACCESS_READ | TEE_MEMORY_ACCESS_ANY_OWNER;
2599c99bb1dSJens Wiklander 	TEE_Result res = TEE_SUCCESS;
2609c99bb1dSJens Wiklander 	size_t l = 0;
2619c99bb1dSJens Wiklander 	char *d = NULL;
2629c99bb1dSJens Wiklander 
2639c99bb1dSJens Wiklander 	src = memtag_strip_tag_const(src);
264e59bc1dbSJens Wiklander 	res = check_user_access(flags, src, maxlen);
2659c99bb1dSJens Wiklander 	if (res)
2669c99bb1dSJens Wiklander 		return res;
2674e154320SSeonghyun Park 
2684e154320SSeonghyun Park 	enter_user_access();
2699c99bb1dSJens Wiklander 	l = strnlen(src, maxlen);
2704e154320SSeonghyun Park 	exit_user_access();
2714e154320SSeonghyun Park 
2729c99bb1dSJens Wiklander 	d = bb_alloc(l + 1);
2739c99bb1dSJens Wiklander 	if (!d)
2749c99bb1dSJens Wiklander 		return TEE_ERROR_OUT_OF_MEMORY;
2754e154320SSeonghyun Park 
2764e154320SSeonghyun Park 	enter_user_access();
2779c99bb1dSJens Wiklander 	memcpy(d, src, l);
2784e154320SSeonghyun Park 	exit_user_access();
2794e154320SSeonghyun Park 
2809c99bb1dSJens Wiklander 	d[l] = 0;
2819c99bb1dSJens Wiklander 
2829c99bb1dSJens Wiklander 	*dst = d;
2839c99bb1dSJens Wiklander 	*dstlen = l;
2849c99bb1dSJens Wiklander 	return TEE_SUCCESS;
2859c99bb1dSJens Wiklander }
2869c99bb1dSJens Wiklander 
287c40a6505SJens Wiklander TEE_Result copy_kaddr_to_uref(uint32_t *uref, void *kaddr)
288c40a6505SJens Wiklander {
289c40a6505SJens Wiklander 	uint32_t ref = kaddr_to_uref(kaddr);
290c40a6505SJens Wiklander 
2917e4100f3SJens Wiklander 	return copy_to_user_private(uref, &ref, sizeof(ref));
292c40a6505SJens Wiklander }
293c40a6505SJens Wiklander 
294c40a6505SJens Wiklander uint32_t kaddr_to_uref(void *kaddr)
295c40a6505SJens Wiklander {
296a0e8ffe9SJens Wiklander 	if (MEMTAG_IS_ENABLED) {
297a0e8ffe9SJens Wiklander 		unsigned int uref_tag_shift = 32 - MEMTAG_TAG_WIDTH;
298a0e8ffe9SJens Wiklander 		vaddr_t uref = memtag_strip_tag_vaddr(kaddr);
299a0e8ffe9SJens Wiklander 
300a0e8ffe9SJens Wiklander 		uref -= VCORE_START_VA;
301a0e8ffe9SJens Wiklander 		assert(uref < (UINT32_MAX >> MEMTAG_TAG_WIDTH));
302a0e8ffe9SJens Wiklander 		uref |= memtag_get_tag(kaddr) << uref_tag_shift;
303a0e8ffe9SJens Wiklander 		return uref;
304a0e8ffe9SJens Wiklander 	}
305a0e8ffe9SJens Wiklander 
306c40a6505SJens Wiklander 	assert(((vaddr_t)kaddr - VCORE_START_VA) < UINT32_MAX);
307c40a6505SJens Wiklander 	return (vaddr_t)kaddr - VCORE_START_VA;
308c40a6505SJens Wiklander }
309c40a6505SJens Wiklander 
310c40a6505SJens Wiklander vaddr_t uref_to_vaddr(uint32_t uref)
311c40a6505SJens Wiklander {
312a0e8ffe9SJens Wiklander 	if (MEMTAG_IS_ENABLED) {
313a0e8ffe9SJens Wiklander 		vaddr_t u = uref & (UINT32_MAX >> MEMTAG_TAG_WIDTH);
314a0e8ffe9SJens Wiklander 		unsigned int uref_tag_shift = 32 - MEMTAG_TAG_WIDTH;
315a0e8ffe9SJens Wiklander 		uint8_t tag = uref >> uref_tag_shift;
316a0e8ffe9SJens Wiklander 
317a0e8ffe9SJens Wiklander 		return memtag_insert_tag_vaddr(VCORE_START_VA + u, tag);
318a0e8ffe9SJens Wiklander 	}
319a0e8ffe9SJens Wiklander 
320c40a6505SJens Wiklander 	return VCORE_START_VA + uref;
321c40a6505SJens Wiklander }
322