Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
[linux-2.6-microblaze.git] / include / linux / sockptr.h
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 */