xref: /linux-6.15/include/linux/sockptr.h (revision aaa3e7fb)
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 #else /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */
31 typedef struct {
32 	union {
33 		void		*kernel;
34 		void __user	*user;
35 	};
36 	bool		is_kernel : 1;
37 } sockptr_t;
38 
39 static inline bool sockptr_is_kernel(sockptr_t sockptr)
40 {
41 	return sockptr.is_kernel;
42 }
43 
44 static inline sockptr_t KERNEL_SOCKPTR(void *p)
45 {
46 	return (sockptr_t) { .kernel = p, .is_kernel = true };
47 }
48 #endif /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */
49 
50 static inline int __must_check init_user_sockptr(sockptr_t *sp, void __user *p,
51 		size_t size)
52 {
53 	if (!access_ok(p, size))
54 		return -EFAULT;
55 	*sp = (sockptr_t) { .user = p };
56 	return 0;
57 }
58 
59 static inline bool sockptr_is_null(sockptr_t sockptr)
60 {
61 	if (sockptr_is_kernel(sockptr))
62 		return !sockptr.kernel;
63 	return !sockptr.user;
64 }
65 
66 static inline int copy_from_sockptr_offset(void *dst, sockptr_t src,
67 		size_t offset, size_t size)
68 {
69 	if (!sockptr_is_kernel(src))
70 		return copy_from_user(dst, src.user + offset, size);
71 	memcpy(dst, src.kernel + offset, size);
72 	return 0;
73 }
74 
75 static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size)
76 {
77 	return copy_from_sockptr_offset(dst, src, 0, size);
78 }
79 
80 static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset,
81 		const void *src, size_t size)
82 {
83 	if (!sockptr_is_kernel(dst))
84 		return copy_to_user(dst.user + offset, src, size);
85 	memcpy(dst.kernel + offset, src, size);
86 	return 0;
87 }
88 
89 static inline void *memdup_sockptr(sockptr_t src, size_t len)
90 {
91 	void *p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN);
92 
93 	if (!p)
94 		return ERR_PTR(-ENOMEM);
95 	if (copy_from_sockptr(p, src, len)) {
96 		kfree(p);
97 		return ERR_PTR(-EFAULT);
98 	}
99 	return p;
100 }
101 
102 static inline void *memdup_sockptr_nul(sockptr_t src, size_t len)
103 {
104 	char *p = kmalloc_track_caller(len + 1, GFP_KERNEL);
105 
106 	if (!p)
107 		return ERR_PTR(-ENOMEM);
108 	if (copy_from_sockptr(p, src, len)) {
109 		kfree(p);
110 		return ERR_PTR(-EFAULT);
111 	}
112 	p[len] = '\0';
113 	return p;
114 }
115 
116 static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count)
117 {
118 	if (sockptr_is_kernel(src)) {
119 		size_t len = min(strnlen(src.kernel, count - 1) + 1, count);
120 
121 		memcpy(dst, src.kernel, len);
122 		return len;
123 	}
124 	return strncpy_from_user(dst, src.user, count);
125 }
126 
127 #endif /* _LINUX_SOCKPTR_H */
128