xref: /linux-6.15/include/linux/sockptr.h (revision 348cb5dc)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * Copyright (c) 2020 Christoph Hellwig.
4  *
5  * Support for "universal" pointers that can point to either kernel or userspace
6  * memory.
7  */
8 #ifndef _LINUX_SOCKPTR_H
9 #define _LINUX_SOCKPTR_H
10 
11 #include <linux/compiler.h>
12 #include <linux/slab.h>
13 #include <linux/uaccess.h>
14 
15 #ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
16 typedef union {
17 	void		*kernel;
18 	void __user	*user;
19 } sockptr_t;
20 
21 static inline bool sockptr_is_kernel(sockptr_t sockptr)
22 {
23 	return (unsigned long)sockptr.kernel >= TASK_SIZE;
24 }
25 
26 static inline sockptr_t KERNEL_SOCKPTR(void *p)
27 {
28 	return (sockptr_t) { .kernel = p };
29 }
30 
31 static inline int __must_check init_user_sockptr(sockptr_t *sp, void __user *p)
32 {
33 	if ((unsigned long)p >= TASK_SIZE)
34 		return -EFAULT;
35 	sp->user = p;
36 	return 0;
37 }
38 #else /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */
39 typedef struct {
40 	union {
41 		void		*kernel;
42 		void __user	*user;
43 	};
44 	bool		is_kernel : 1;
45 } sockptr_t;
46 
47 static inline bool sockptr_is_kernel(sockptr_t sockptr)
48 {
49 	return sockptr.is_kernel;
50 }
51 
52 static inline sockptr_t KERNEL_SOCKPTR(void *p)
53 {
54 	return (sockptr_t) { .kernel = p, .is_kernel = true };
55 }
56 
57 static inline int __must_check init_user_sockptr(sockptr_t *sp, void __user *p)
58 {
59 	sp->user = p;
60 	sp->is_kernel = false;
61 	return 0;
62 }
63 #endif /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */
64 
65 static inline bool sockptr_is_null(sockptr_t sockptr)
66 {
67 	return !sockptr.user && !sockptr.kernel;
68 }
69 
70 static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size)
71 {
72 	if (!sockptr_is_kernel(src))
73 		return copy_from_user(dst, src.user, size);
74 	memcpy(dst, src.kernel, size);
75 	return 0;
76 }
77 
78 static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size)
79 {
80 	if (!sockptr_is_kernel(dst))
81 		return copy_to_user(dst.user, src, size);
82 	memcpy(dst.kernel, src, size);
83 	return 0;
84 }
85 
86 static inline void *memdup_sockptr(sockptr_t src, size_t len)
87 {
88 	void *p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN);
89 
90 	if (!p)
91 		return ERR_PTR(-ENOMEM);
92 	if (copy_from_sockptr(p, src, len)) {
93 		kfree(p);
94 		return ERR_PTR(-EFAULT);
95 	}
96 	return p;
97 }
98 
99 static inline void *memdup_sockptr_nul(sockptr_t src, size_t len)
100 {
101 	char *p = kmalloc_track_caller(len + 1, GFP_KERNEL);
102 
103 	if (!p)
104 		return ERR_PTR(-ENOMEM);
105 	if (copy_from_sockptr(p, src, len)) {
106 		kfree(p);
107 		return ERR_PTR(-EFAULT);
108 	}
109 	p[len] = '\0';
110 	return p;
111 }
112 
113 static inline void sockptr_advance(sockptr_t sockptr, size_t len)
114 {
115 	if (sockptr_is_kernel(sockptr))
116 		sockptr.kernel += len;
117 	else
118 		sockptr.user += len;
119 }
120 
121 static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count)
122 {
123 	if (sockptr_is_kernel(src)) {
124 		size_t len = min(strnlen(src.kernel, count - 1) + 1, count);
125 
126 		memcpy(dst, src.kernel, len);
127 		return len;
128 	}
129 	return strncpy_from_user(dst, src.user, count);
130 }
131 
132 #endif /* _LINUX_SOCKPTR_H */
133