]>
Commit | Line | Data |
---|---|---|
d07dcf9a JB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * NETLINK Policy advertisement to userspace | |
4 | * | |
5 | * Authors: Johannes Berg <johannes@sipsolutions.net> | |
6 | * | |
7 | * Copyright 2019 Intel Corporation | |
8 | */ | |
9 | ||
10 | #include <linux/kernel.h> | |
11 | #include <linux/errno.h> | |
12 | #include <linux/types.h> | |
13 | #include <net/netlink.h> | |
14 | ||
15 | #define INITIAL_POLICIES_ALLOC 10 | |
16 | ||
17 | struct nl_policy_dump { | |
18 | unsigned int policy_idx; | |
19 | unsigned int attr_idx; | |
20 | unsigned int n_alloc; | |
21 | struct { | |
22 | const struct nla_policy *policy; | |
23 | unsigned int maxtype; | |
24 | } policies[]; | |
25 | }; | |
26 | ||
27 | static int add_policy(struct nl_policy_dump **statep, | |
28 | const struct nla_policy *policy, | |
29 | unsigned int maxtype) | |
30 | { | |
31 | struct nl_policy_dump *state = *statep; | |
32 | unsigned int n_alloc, i; | |
33 | ||
34 | if (!policy || !maxtype) | |
35 | return 0; | |
36 | ||
37 | for (i = 0; i < state->n_alloc; i++) { | |
38 | if (state->policies[i].policy == policy) | |
39 | return 0; | |
40 | ||
41 | if (!state->policies[i].policy) { | |
42 | state->policies[i].policy = policy; | |
43 | state->policies[i].maxtype = maxtype; | |
44 | return 0; | |
45 | } | |
46 | } | |
47 | ||
48 | n_alloc = state->n_alloc + INITIAL_POLICIES_ALLOC; | |
49 | state = krealloc(state, struct_size(state, policies, n_alloc), | |
50 | GFP_KERNEL); | |
51 | if (!state) | |
52 | return -ENOMEM; | |
53 | ||
54 | state->policies[state->n_alloc].policy = policy; | |
55 | state->policies[state->n_alloc].maxtype = maxtype; | |
56 | state->n_alloc = n_alloc; | |
57 | *statep = state; | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | static unsigned int get_policy_idx(struct nl_policy_dump *state, | |
63 | const struct nla_policy *policy) | |
64 | { | |
65 | unsigned int i; | |
66 | ||
67 | for (i = 0; i < state->n_alloc; i++) { | |
68 | if (state->policies[i].policy == policy) | |
69 | return i; | |
70 | } | |
71 | ||
72 | WARN_ON_ONCE(1); | |
73 | return -1; | |
74 | } | |
75 | ||
76 | int netlink_policy_dump_start(const struct nla_policy *policy, | |
77 | unsigned int maxtype, | |
78 | unsigned long *_state) | |
79 | { | |
80 | struct nl_policy_dump *state; | |
81 | unsigned int policy_idx; | |
82 | int err; | |
83 | ||
84 | /* also returns 0 if "*_state" is our ERR_PTR() end marker */ | |
85 | if (*_state) | |
86 | return 0; | |
87 | ||
88 | /* | |
89 | * walk the policies and nested ones first, and build | |
90 | * a linear list of them. | |
91 | */ | |
92 | ||
93 | state = kzalloc(struct_size(state, policies, INITIAL_POLICIES_ALLOC), | |
94 | GFP_KERNEL); | |
95 | if (!state) | |
96 | return -ENOMEM; | |
97 | state->n_alloc = INITIAL_POLICIES_ALLOC; | |
98 | ||
99 | err = add_policy(&state, policy, maxtype); | |
100 | if (err) | |
101 | return err; | |
102 | ||
103 | for (policy_idx = 0; | |
104 | policy_idx < state->n_alloc && state->policies[policy_idx].policy; | |
105 | policy_idx++) { | |
106 | const struct nla_policy *policy; | |
107 | unsigned int type; | |
108 | ||
109 | policy = state->policies[policy_idx].policy; | |
110 | ||
111 | for (type = 0; | |
112 | type <= state->policies[policy_idx].maxtype; | |
113 | type++) { | |
114 | switch (policy[type].type) { | |
115 | case NLA_NESTED: | |
116 | case NLA_NESTED_ARRAY: | |
117 | err = add_policy(&state, | |
118 | policy[type].nested_policy, | |
119 | policy[type].len); | |
120 | if (err) | |
121 | return err; | |
122 | break; | |
123 | default: | |
124 | break; | |
125 | } | |
126 | } | |
127 | } | |
128 | ||
129 | *_state = (unsigned long)state; | |
130 | ||
131 | return 0; | |
132 | } | |
133 | ||
134 | static bool netlink_policy_dump_finished(struct nl_policy_dump *state) | |
135 | { | |
136 | return state->policy_idx >= state->n_alloc || | |
137 | !state->policies[state->policy_idx].policy; | |
138 | } | |
139 | ||
140 | bool netlink_policy_dump_loop(unsigned long *_state) | |
141 | { | |
142 | struct nl_policy_dump *state = (void *)*_state; | |
143 | ||
144 | if (IS_ERR(state)) | |
145 | return false; | |
146 | ||
147 | if (netlink_policy_dump_finished(state)) { | |
148 | kfree(state); | |
149 | /* store end marker instead of freed state */ | |
150 | *_state = (unsigned long)ERR_PTR(-ENOENT); | |
151 | return false; | |
152 | } | |
153 | ||
154 | return true; | |
155 | } | |
156 | ||
157 | int netlink_policy_dump_write(struct sk_buff *skb, unsigned long _state) | |
158 | { | |
159 | struct nl_policy_dump *state = (void *)_state; | |
160 | const struct nla_policy *pt; | |
161 | struct nlattr *policy, *attr; | |
162 | enum netlink_attribute_type type; | |
163 | bool again; | |
164 | ||
165 | send_attribute: | |
166 | again = false; | |
167 | ||
168 | pt = &state->policies[state->policy_idx].policy[state->attr_idx]; | |
169 | ||
170 | policy = nla_nest_start(skb, state->policy_idx); | |
171 | if (!policy) | |
172 | return -ENOBUFS; | |
173 | ||
174 | attr = nla_nest_start(skb, state->attr_idx); | |
175 | if (!attr) | |
176 | goto nla_put_failure; | |
177 | ||
178 | switch (pt->type) { | |
179 | default: | |
180 | case NLA_UNSPEC: | |
181 | case NLA_REJECT: | |
182 | /* skip - use NLA_MIN_LEN to advertise such */ | |
183 | nla_nest_cancel(skb, policy); | |
184 | again = true; | |
185 | goto next; | |
186 | case NLA_NESTED: | |
187 | type = NL_ATTR_TYPE_NESTED; | |
188 | /* fall through */ | |
189 | case NLA_NESTED_ARRAY: | |
190 | if (pt->type == NLA_NESTED_ARRAY) | |
191 | type = NL_ATTR_TYPE_NESTED_ARRAY; | |
192 | if (pt->nested_policy && pt->len && | |
193 | (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_POLICY_IDX, | |
194 | get_policy_idx(state, pt->nested_policy)) || | |
195 | nla_put_u32(skb, NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE, | |
196 | pt->len))) | |
197 | goto nla_put_failure; | |
198 | break; | |
199 | case NLA_U8: | |
200 | case NLA_U16: | |
201 | case NLA_U32: | |
202 | case NLA_U64: | |
203 | case NLA_MSECS: { | |
204 | struct netlink_range_validation range; | |
205 | ||
206 | if (pt->type == NLA_U8) | |
207 | type = NL_ATTR_TYPE_U8; | |
208 | else if (pt->type == NLA_U16) | |
209 | type = NL_ATTR_TYPE_U16; | |
210 | else if (pt->type == NLA_U32) | |
211 | type = NL_ATTR_TYPE_U32; | |
212 | else | |
213 | type = NL_ATTR_TYPE_U64; | |
214 | ||
215 | nla_get_range_unsigned(pt, &range); | |
216 | ||
217 | if (nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MIN_VALUE_U, | |
218 | range.min, NL_POLICY_TYPE_ATTR_PAD) || | |
219 | nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MAX_VALUE_U, | |
220 | range.max, NL_POLICY_TYPE_ATTR_PAD)) | |
221 | goto nla_put_failure; | |
222 | break; | |
223 | } | |
224 | case NLA_S8: | |
225 | case NLA_S16: | |
226 | case NLA_S32: | |
227 | case NLA_S64: { | |
228 | struct netlink_range_validation_signed range; | |
229 | ||
230 | if (pt->type == NLA_S8) | |
231 | type = NL_ATTR_TYPE_S8; | |
232 | else if (pt->type == NLA_S16) | |
233 | type = NL_ATTR_TYPE_S16; | |
234 | else if (pt->type == NLA_S32) | |
235 | type = NL_ATTR_TYPE_S32; | |
236 | else | |
237 | type = NL_ATTR_TYPE_S64; | |
238 | ||
239 | nla_get_range_signed(pt, &range); | |
240 | ||
241 | if (nla_put_s64(skb, NL_POLICY_TYPE_ATTR_MIN_VALUE_S, | |
242 | range.min, NL_POLICY_TYPE_ATTR_PAD) || | |
243 | nla_put_s64(skb, NL_POLICY_TYPE_ATTR_MAX_VALUE_S, | |
244 | range.max, NL_POLICY_TYPE_ATTR_PAD)) | |
245 | goto nla_put_failure; | |
246 | break; | |
247 | } | |
248 | case NLA_BITFIELD32: | |
249 | type = NL_ATTR_TYPE_BITFIELD32; | |
250 | if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_BITFIELD32_MASK, | |
251 | pt->bitfield32_valid)) | |
252 | goto nla_put_failure; | |
253 | break; | |
254 | case NLA_EXACT_LEN: | |
255 | type = NL_ATTR_TYPE_BINARY; | |
256 | if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MIN_LENGTH, pt->len) || | |
257 | nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MAX_LENGTH, pt->len)) | |
258 | goto nla_put_failure; | |
259 | break; | |
260 | case NLA_STRING: | |
261 | case NLA_NUL_STRING: | |
262 | case NLA_BINARY: | |
263 | if (pt->type == NLA_STRING) | |
264 | type = NL_ATTR_TYPE_STRING; | |
265 | else if (pt->type == NLA_NUL_STRING) | |
266 | type = NL_ATTR_TYPE_NUL_STRING; | |
267 | else | |
268 | type = NL_ATTR_TYPE_BINARY; | |
269 | if (pt->len && nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MAX_LENGTH, | |
270 | pt->len)) | |
271 | goto nla_put_failure; | |
272 | break; | |
273 | case NLA_MIN_LEN: | |
274 | type = NL_ATTR_TYPE_BINARY; | |
275 | if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MIN_LENGTH, pt->len)) | |
276 | goto nla_put_failure; | |
277 | break; | |
278 | case NLA_FLAG: | |
279 | type = NL_ATTR_TYPE_FLAG; | |
280 | break; | |
281 | } | |
282 | ||
283 | if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_TYPE, type)) | |
284 | goto nla_put_failure; | |
285 | ||
286 | /* finish and move state to next attribute */ | |
287 | nla_nest_end(skb, attr); | |
288 | nla_nest_end(skb, policy); | |
289 | ||
290 | next: | |
291 | state->attr_idx += 1; | |
292 | if (state->attr_idx > state->policies[state->policy_idx].maxtype) { | |
293 | state->attr_idx = 0; | |
294 | state->policy_idx++; | |
295 | } | |
296 | ||
297 | if (again) { | |
298 | if (netlink_policy_dump_finished(state)) | |
299 | return -ENODATA; | |
300 | goto send_attribute; | |
301 | } | |
302 | ||
303 | return 0; | |
304 | ||
305 | nla_put_failure: | |
306 | nla_nest_cancel(skb, policy); | |
307 | return -ENOBUFS; | |
308 | } |