]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
f6ebe77f HW |
2 | #include <linux/kernel.h> |
3 | #include <linux/init.h> | |
4 | #include <linux/module.h> | |
5 | #include <linux/skbuff.h> | |
6 | #include <linux/netfilter.h> | |
4a3e2f71 | 7 | #include <linux/mutex.h> |
f6ebe77f HW |
8 | #include <net/sock.h> |
9 | ||
10 | #include "nf_internals.h" | |
11 | ||
12 | /* Sockopts only registered and called from user context, so | |
13 | net locking would be overkill. Also, [gs]etsockopt calls may | |
14 | sleep. */ | |
4a3e2f71 | 15 | static DEFINE_MUTEX(nf_sockopt_mutex); |
f6ebe77f HW |
16 | static LIST_HEAD(nf_sockopts); |
17 | ||
18 | /* Do exclusive ranges overlap? */ | |
19 | static inline int overlap(int min1, int max1, int min2, int max2) | |
20 | { | |
21 | return max1 > min2 && min1 < max2; | |
22 | } | |
23 | ||
24 | /* Functions to register sockopt ranges (exclusive). */ | |
25 | int nf_register_sockopt(struct nf_sockopt_ops *reg) | |
26 | { | |
55d84acd | 27 | struct nf_sockopt_ops *ops; |
f6ebe77f HW |
28 | int ret = 0; |
29 | ||
7926dbfa | 30 | mutex_lock(&nf_sockopt_mutex); |
55d84acd | 31 | list_for_each_entry(ops, &nf_sockopts, list) { |
f6ebe77f | 32 | if (ops->pf == reg->pf |
601e68e1 | 33 | && (overlap(ops->set_optmin, ops->set_optmax, |
f6ebe77f | 34 | reg->set_optmin, reg->set_optmax) |
601e68e1 | 35 | || overlap(ops->get_optmin, ops->get_optmax, |
f6ebe77f | 36 | reg->get_optmin, reg->get_optmax))) { |
b38cf901 | 37 | pr_debug("nf_sock overlap: %u-%u/%u-%u v %u-%u/%u-%u\n", |
601e68e1 YH |
38 | ops->set_optmin, ops->set_optmax, |
39 | ops->get_optmin, ops->get_optmax, | |
f6ebe77f HW |
40 | reg->set_optmin, reg->set_optmax, |
41 | reg->get_optmin, reg->get_optmax); | |
42 | ret = -EBUSY; | |
43 | goto out; | |
44 | } | |
45 | } | |
46 | ||
47 | list_add(®->list, &nf_sockopts); | |
48 | out: | |
4a3e2f71 | 49 | mutex_unlock(&nf_sockopt_mutex); |
f6ebe77f HW |
50 | return ret; |
51 | } | |
52 | EXPORT_SYMBOL(nf_register_sockopt); | |
53 | ||
54 | void nf_unregister_sockopt(struct nf_sockopt_ops *reg) | |
55 | { | |
4a3e2f71 | 56 | mutex_lock(&nf_sockopt_mutex); |
f6ebe77f | 57 | list_del(®->list); |
4a3e2f71 | 58 | mutex_unlock(&nf_sockopt_mutex); |
f6ebe77f HW |
59 | } |
60 | EXPORT_SYMBOL(nf_unregister_sockopt); | |
61 | ||
76108cea | 62 | static struct nf_sockopt_ops *nf_sockopt_find(struct sock *sk, u_int8_t pf, |
4ce5ba6a | 63 | int val, int get) |
f6ebe77f | 64 | { |
f6ebe77f | 65 | struct nf_sockopt_ops *ops; |
f6ebe77f | 66 | |
7926dbfa | 67 | mutex_lock(&nf_sockopt_mutex); |
55d84acd | 68 | list_for_each_entry(ops, &nf_sockopts, list) { |
f6ebe77f | 69 | if (ops->pf == pf) { |
16fcec35 NH |
70 | if (!try_module_get(ops->owner)) |
71 | goto out_nosup; | |
4ce5ba6a | 72 | |
f6ebe77f | 73 | if (get) { |
4ce5ba6a PE |
74 | if (val >= ops->get_optmin && |
75 | val < ops->get_optmax) | |
f6ebe77f | 76 | goto out; |
f6ebe77f | 77 | } else { |
4ce5ba6a PE |
78 | if (val >= ops->set_optmin && |
79 | val < ops->set_optmax) | |
f6ebe77f | 80 | goto out; |
f6ebe77f | 81 | } |
16fcec35 | 82 | module_put(ops->owner); |
f6ebe77f HW |
83 | } |
84 | } | |
4ce5ba6a PE |
85 | out_nosup: |
86 | ops = ERR_PTR(-ENOPROTOOPT); | |
87 | out: | |
4a3e2f71 | 88 | mutex_unlock(&nf_sockopt_mutex); |
4ce5ba6a PE |
89 | return ops; |
90 | } | |
91 | ||
92 | /* Call get/setsockopt() */ | |
76108cea | 93 | static int nf_sockopt(struct sock *sk, u_int8_t pf, int val, |
4ce5ba6a PE |
94 | char __user *opt, int *len, int get) |
95 | { | |
96 | struct nf_sockopt_ops *ops; | |
97 | int ret; | |
98 | ||
99 | ops = nf_sockopt_find(sk, pf, val, get); | |
100 | if (IS_ERR(ops)) | |
101 | return PTR_ERR(ops); | |
102 | ||
103 | if (get) | |
104 | ret = ops->get(sk, val, opt, len); | |
105 | else | |
106 | ret = ops->set(sk, val, opt, *len); | |
601e68e1 | 107 | |
16fcec35 | 108 | module_put(ops->owner); |
f6ebe77f HW |
109 | return ret; |
110 | } | |
111 | ||
76108cea | 112 | int nf_setsockopt(struct sock *sk, u_int8_t pf, int val, char __user *opt, |
b7058842 | 113 | unsigned int len) |
f6ebe77f HW |
114 | { |
115 | return nf_sockopt(sk, pf, val, opt, &len, 0); | |
116 | } | |
117 | EXPORT_SYMBOL(nf_setsockopt); | |
118 | ||
76108cea JE |
119 | int nf_getsockopt(struct sock *sk, u_int8_t pf, int val, char __user *opt, |
120 | int *len) | |
f6ebe77f HW |
121 | { |
122 | return nf_sockopt(sk, pf, val, opt, len, 1); | |
123 | } | |
124 | EXPORT_SYMBOL(nf_getsockopt); | |
125 | ||
3fdadf7d | 126 | #ifdef CONFIG_COMPAT |
76108cea | 127 | static int compat_nf_sockopt(struct sock *sk, u_int8_t pf, int val, |
543d9cfe | 128 | char __user *opt, int *len, int get) |
3fdadf7d | 129 | { |
3fdadf7d DM |
130 | struct nf_sockopt_ops *ops; |
131 | int ret; | |
132 | ||
4ce5ba6a PE |
133 | ops = nf_sockopt_find(sk, pf, val, get); |
134 | if (IS_ERR(ops)) | |
135 | return PTR_ERR(ops); | |
136 | ||
137 | if (get) { | |
138 | if (ops->compat_get) | |
139 | ret = ops->compat_get(sk, val, opt, len); | |
140 | else | |
6452a5fd | 141 | ret = ops->get(sk, val, opt, len); |
4ce5ba6a PE |
142 | } else { |
143 | if (ops->compat_set) | |
6452a5fd | 144 | ret = ops->compat_set(sk, val, opt, *len); |
4ce5ba6a | 145 | else |
6452a5fd | 146 | ret = ops->set(sk, val, opt, *len); |
3fdadf7d | 147 | } |
3fdadf7d | 148 | |
16fcec35 | 149 | module_put(ops->owner); |
3fdadf7d DM |
150 | return ret; |
151 | } | |
152 | ||
76108cea | 153 | int compat_nf_setsockopt(struct sock *sk, u_int8_t pf, |
b7058842 | 154 | int val, char __user *opt, unsigned int len) |
3fdadf7d DM |
155 | { |
156 | return compat_nf_sockopt(sk, pf, val, opt, &len, 0); | |
157 | } | |
158 | EXPORT_SYMBOL(compat_nf_setsockopt); | |
159 | ||
76108cea | 160 | int compat_nf_getsockopt(struct sock *sk, u_int8_t pf, |
3fdadf7d DM |
161 | int val, char __user *opt, int *len) |
162 | { | |
163 | return compat_nf_sockopt(sk, pf, val, opt, len, 1); | |
164 | } | |
165 | EXPORT_SYMBOL(compat_nf_getsockopt); | |
166 | #endif |