xref: /linux-6.15/include/linux/sockptr.h (revision 035bfd05)
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 	if (sockptr_is_kernel(sockptr))
68 		return !sockptr.kernel;
69 	return !sockptr.user;
70 }
71 
72 static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size)
73 {
74 	if (!sockptr_is_kernel(src))
75 		return copy_from_user(dst, src.user, size);
76 	memcpy(dst, src.kernel, size);
77 	return 0;
78 }
79 
80 static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size)
81 {
82 	if (!sockptr_is_kernel(dst))
83 		return copy_to_user(dst.user, src, size);
84 	memcpy(dst.kernel, src, size);
85 	return 0;
86 }
87 
88 static inline void *memdup_sockptr(sockptr_t src, size_t len)
89 {
90 	void *p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN);
91 
92 	if (!p)
93 		return ERR_PTR(-ENOMEM);
94 	if (copy_from_sockptr(p, src, len)) {
95 		kfree(p);
96 		return ERR_PTR(-EFAULT);
97 	}
98 	return p;
99 }
100 
101 static inline void *memdup_sockptr_nul(sockptr_t src, size_t len)
102 {
103 	char *p = kmalloc_track_caller(len + 1, GFP_KERNEL);
104 
105 	if (!p)
106 		return ERR_PTR(-ENOMEM);
107 	if (copy_from_sockptr(p, src, len)) {
108 		kfree(p);
109 		return ERR_PTR(-EFAULT);
110 	}
111 	p[len] = '\0';
112 	return p;
113 }
114 
115 static inline void sockptr_advance(sockptr_t sockptr, size_t len)
116 {
117 	if (sockptr_is_kernel(sockptr))
118 		sockptr.kernel += len;
119 	else
120 		sockptr.user += len;
121 }
122 
123 static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count)
124 {
125 	if (sockptr_is_kernel(src)) {
126 		size_t len = min(strnlen(src.kernel, count - 1) + 1, count);
127 
128 		memcpy(dst, src.kernel, len);
129 		return len;
130 	}
131 	return strncpy_from_user(dst, src.user, count);
132 }
133 
134 #endif /* _LINUX_SOCKPTR_H */
135