Commit | Line | Data |
---|---|---|
09c434b8 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
a257cdd0 AG |
2 | /* |
3 | * fs/nfs_common/nfsacl.c | |
4 | * | |
5 | * Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de> | |
6 | */ | |
7 | ||
8 | /* | |
9 | * The Solaris nfsacl protocol represents some ACLs slightly differently | |
10 | * than POSIX 1003.1e draft 17 does (and we do): | |
11 | * | |
12 | * - Minimal ACLs always have an ACL_MASK entry, so they have | |
13 | * four instead of three entries. | |
14 | * - The ACL_MASK entry in such minimal ACLs always has the same | |
15 | * permissions as the ACL_GROUP_OBJ entry. (In extended ACLs | |
16 | * the ACL_MASK and ACL_GROUP_OBJ entries may differ.) | |
17 | * - The identifier fields of the ACL_USER_OBJ and ACL_GROUP_OBJ | |
18 | * entries contain the identifiers of the owner and owning group. | |
19 | * (In POSIX ACLs we always set them to ACL_UNDEFINED_ID). | |
20 | * - ACL entries in the kernel are kept sorted in ascending order | |
21 | * of (e_tag, e_id). Solaris ACLs are unsorted. | |
22 | */ | |
23 | ||
24 | #include <linux/module.h> | |
25 | #include <linux/fs.h> | |
5a0e3ad6 | 26 | #include <linux/gfp.h> |
a257cdd0 AG |
27 | #include <linux/sunrpc/xdr.h> |
28 | #include <linux/nfsacl.h> | |
29 | #include <linux/nfs3.h> | |
30 | #include <linux/sort.h> | |
31 | ||
d3318990 | 32 | MODULE_DESCRIPTION("NFS ACL support"); |
a257cdd0 AG |
33 | MODULE_LICENSE("GPL"); |
34 | ||
a257cdd0 AG |
35 | struct nfsacl_encode_desc { |
36 | struct xdr_array2_desc desc; | |
37 | unsigned int count; | |
38 | struct posix_acl *acl; | |
39 | int typeflag; | |
ddca4e17 EB |
40 | kuid_t uid; |
41 | kgid_t gid; | |
a257cdd0 AG |
42 | }; |
43 | ||
f61f6da0 CL |
44 | struct nfsacl_simple_acl { |
45 | struct posix_acl acl; | |
46 | struct posix_acl_entry ace[4]; | |
47 | }; | |
48 | ||
a257cdd0 AG |
49 | static int |
50 | xdr_nfsace_encode(struct xdr_array2_desc *desc, void *elem) | |
51 | { | |
52 | struct nfsacl_encode_desc *nfsacl_desc = | |
53 | (struct nfsacl_encode_desc *) desc; | |
83bbe2ef | 54 | __be32 *p = elem; |
a257cdd0 | 55 | |
22c1ea44 AG |
56 | struct posix_acl_entry *entry = |
57 | &nfsacl_desc->acl->a_entries[nfsacl_desc->count++]; | |
a257cdd0 | 58 | |
22c1ea44 AG |
59 | *p++ = htonl(entry->e_tag | nfsacl_desc->typeflag); |
60 | switch(entry->e_tag) { | |
61 | case ACL_USER_OBJ: | |
ddca4e17 | 62 | *p++ = htonl(from_kuid(&init_user_ns, nfsacl_desc->uid)); |
22c1ea44 AG |
63 | break; |
64 | case ACL_GROUP_OBJ: | |
ddca4e17 | 65 | *p++ = htonl(from_kgid(&init_user_ns, nfsacl_desc->gid)); |
22c1ea44 AG |
66 | break; |
67 | case ACL_USER: | |
ddca4e17 EB |
68 | *p++ = htonl(from_kuid(&init_user_ns, entry->e_uid)); |
69 | break; | |
22c1ea44 | 70 | case ACL_GROUP: |
ddca4e17 | 71 | *p++ = htonl(from_kgid(&init_user_ns, entry->e_gid)); |
22c1ea44 AG |
72 | break; |
73 | default: /* Solaris depends on that! */ | |
74 | *p++ = 0; | |
75 | break; | |
a257cdd0 | 76 | } |
22c1ea44 | 77 | *p++ = htonl(entry->e_perm & S_IRWXO); |
a257cdd0 AG |
78 | return 0; |
79 | } | |
80 | ||
731f3f48 CL |
81 | /** |
82 | * nfsacl_encode - Encode an NFSv3 ACL | |
83 | * | |
84 | * @buf: destination xdr_buf to contain XDR encoded ACL | |
85 | * @base: byte offset in xdr_buf where XDR'd ACL begins | |
86 | * @inode: inode of file whose ACL this is | |
87 | * @acl: posix_acl to encode | |
88 | * @encode_entries: whether to encode ACEs as well | |
89 | * @typeflag: ACL type: NFS_ACL_DEFAULT or zero | |
90 | * | |
91 | * Returns size of encoded ACL in bytes or a negative errno value. | |
92 | */ | |
93 | int nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode, | |
94 | struct posix_acl *acl, int encode_entries, int typeflag) | |
a257cdd0 AG |
95 | { |
96 | int entries = (acl && acl->a_count) ? max_t(int, acl->a_count, 4) : 0; | |
97 | struct nfsacl_encode_desc nfsacl_desc = { | |
98 | .desc = { | |
99 | .elem_size = 12, | |
100 | .array_len = encode_entries ? entries : 0, | |
101 | .xcode = xdr_nfsace_encode, | |
102 | }, | |
103 | .acl = acl, | |
104 | .typeflag = typeflag, | |
105 | .uid = inode->i_uid, | |
106 | .gid = inode->i_gid, | |
107 | }; | |
f61f6da0 | 108 | struct nfsacl_simple_acl aclbuf; |
a257cdd0 AG |
109 | int err; |
110 | ||
111 | if (entries > NFS_ACL_MAX_ENTRIES || | |
112 | xdr_encode_word(buf, base, entries)) | |
113 | return -EINVAL; | |
22c1ea44 | 114 | if (encode_entries && acl && acl->a_count == 3) { |
f61f6da0 CL |
115 | struct posix_acl *acl2 = &aclbuf.acl; |
116 | ||
117 | /* Avoid the use of posix_acl_alloc(). nfsacl_encode() is | |
118 | * invoked in contexts where a memory allocation failure is | |
119 | * fatal. Fortunately this fake ACL is small enough to | |
120 | * construct on the stack. */ | |
f61f6da0 CL |
121 | posix_acl_init(acl2, 4); |
122 | ||
22c1ea44 AG |
123 | /* Insert entries in canonical order: other orders seem |
124 | to confuse Solaris VxFS. */ | |
125 | acl2->a_entries[0] = acl->a_entries[0]; /* ACL_USER_OBJ */ | |
126 | acl2->a_entries[1] = acl->a_entries[1]; /* ACL_GROUP_OBJ */ | |
127 | acl2->a_entries[2] = acl->a_entries[1]; /* ACL_MASK */ | |
128 | acl2->a_entries[2].e_tag = ACL_MASK; | |
129 | acl2->a_entries[3] = acl->a_entries[2]; /* ACL_OTHER */ | |
130 | nfsacl_desc.acl = acl2; | |
131 | } | |
a257cdd0 AG |
132 | err = xdr_encode_array2(buf, base + 4, &nfsacl_desc.desc); |
133 | if (!err) | |
134 | err = 8 + nfsacl_desc.desc.elem_size * | |
135 | nfsacl_desc.desc.array_len; | |
136 | return err; | |
137 | } | |
57b696fb | 138 | EXPORT_SYMBOL_GPL(nfsacl_encode); |
a257cdd0 | 139 | |
8edc0648 CL |
140 | /** |
141 | * nfs_stream_encode_acl - Encode an NFSv3 ACL | |
142 | * | |
143 | * @xdr: an xdr_stream positioned to receive an encoded ACL | |
144 | * @inode: inode of file whose ACL this is | |
145 | * @acl: posix_acl to encode | |
146 | * @encode_entries: whether to encode ACEs as well | |
147 | * @typeflag: ACL type: NFS_ACL_DEFAULT or zero | |
148 | * | |
149 | * Return values: | |
150 | * %false: The ACL could not be encoded | |
151 | * %true: @xdr is advanced to the next available position | |
152 | */ | |
153 | bool nfs_stream_encode_acl(struct xdr_stream *xdr, struct inode *inode, | |
154 | struct posix_acl *acl, int encode_entries, | |
155 | int typeflag) | |
156 | { | |
157 | const size_t elem_size = XDR_UNIT * 3; | |
158 | u32 entries = (acl && acl->a_count) ? max_t(int, acl->a_count, 4) : 0; | |
159 | struct nfsacl_encode_desc nfsacl_desc = { | |
160 | .desc = { | |
161 | .elem_size = elem_size, | |
162 | .array_len = encode_entries ? entries : 0, | |
163 | .xcode = xdr_nfsace_encode, | |
164 | }, | |
165 | .acl = acl, | |
166 | .typeflag = typeflag, | |
167 | .uid = inode->i_uid, | |
168 | .gid = inode->i_gid, | |
169 | }; | |
170 | struct nfsacl_simple_acl aclbuf; | |
171 | unsigned int base; | |
172 | int err; | |
173 | ||
174 | if (entries > NFS_ACL_MAX_ENTRIES) | |
175 | return false; | |
176 | if (xdr_stream_encode_u32(xdr, entries) < 0) | |
177 | return false; | |
178 | ||
179 | if (encode_entries && acl && acl->a_count == 3) { | |
180 | struct posix_acl *acl2 = &aclbuf.acl; | |
181 | ||
182 | /* Avoid the use of posix_acl_alloc(). nfsacl_encode() is | |
183 | * invoked in contexts where a memory allocation failure is | |
184 | * fatal. Fortunately this fake ACL is small enough to | |
185 | * construct on the stack. */ | |
186 | posix_acl_init(acl2, 4); | |
187 | ||
188 | /* Insert entries in canonical order: other orders seem | |
189 | to confuse Solaris VxFS. */ | |
190 | acl2->a_entries[0] = acl->a_entries[0]; /* ACL_USER_OBJ */ | |
191 | acl2->a_entries[1] = acl->a_entries[1]; /* ACL_GROUP_OBJ */ | |
192 | acl2->a_entries[2] = acl->a_entries[1]; /* ACL_MASK */ | |
193 | acl2->a_entries[2].e_tag = ACL_MASK; | |
194 | acl2->a_entries[3] = acl->a_entries[2]; /* ACL_OTHER */ | |
195 | nfsacl_desc.acl = acl2; | |
196 | } | |
197 | ||
198 | base = xdr_stream_pos(xdr); | |
199 | if (!xdr_reserve_space(xdr, XDR_UNIT + | |
200 | elem_size * nfsacl_desc.desc.array_len)) | |
201 | return false; | |
202 | err = xdr_encode_array2(xdr->buf, base, &nfsacl_desc.desc); | |
203 | if (err) | |
204 | return false; | |
205 | ||
206 | return true; | |
207 | } | |
208 | EXPORT_SYMBOL_GPL(nfs_stream_encode_acl); | |
209 | ||
210 | ||
a257cdd0 AG |
211 | struct nfsacl_decode_desc { |
212 | struct xdr_array2_desc desc; | |
213 | unsigned int count; | |
214 | struct posix_acl *acl; | |
215 | }; | |
216 | ||
217 | static int | |
218 | xdr_nfsace_decode(struct xdr_array2_desc *desc, void *elem) | |
219 | { | |
220 | struct nfsacl_decode_desc *nfsacl_desc = | |
221 | (struct nfsacl_decode_desc *) desc; | |
83bbe2ef | 222 | __be32 *p = elem; |
a257cdd0 | 223 | struct posix_acl_entry *entry; |
ddca4e17 | 224 | unsigned int id; |
a257cdd0 AG |
225 | |
226 | if (!nfsacl_desc->acl) { | |
227 | if (desc->array_len > NFS_ACL_MAX_ENTRIES) | |
228 | return -EINVAL; | |
229 | nfsacl_desc->acl = posix_acl_alloc(desc->array_len, GFP_KERNEL); | |
230 | if (!nfsacl_desc->acl) | |
231 | return -ENOMEM; | |
232 | nfsacl_desc->count = 0; | |
233 | } | |
234 | ||
235 | entry = &nfsacl_desc->acl->a_entries[nfsacl_desc->count++]; | |
236 | entry->e_tag = ntohl(*p++) & ~NFS_ACL_DEFAULT; | |
ddca4e17 | 237 | id = ntohl(*p++); |
a257cdd0 AG |
238 | entry->e_perm = ntohl(*p++); |
239 | ||
240 | switch(entry->e_tag) { | |
a257cdd0 | 241 | case ACL_USER: |
ddca4e17 EB |
242 | entry->e_uid = make_kuid(&init_user_ns, id); |
243 | if (!uid_valid(entry->e_uid)) | |
244 | return -EINVAL; | |
245 | break; | |
a257cdd0 | 246 | case ACL_GROUP: |
ddca4e17 EB |
247 | entry->e_gid = make_kgid(&init_user_ns, id); |
248 | if (!gid_valid(entry->e_gid)) | |
249 | return -EINVAL; | |
250 | break; | |
251 | case ACL_USER_OBJ: | |
252 | case ACL_GROUP_OBJ: | |
a257cdd0 AG |
253 | case ACL_OTHER: |
254 | if (entry->e_perm & ~S_IRWXO) | |
255 | return -EINVAL; | |
256 | break; | |
257 | case ACL_MASK: | |
25985edc | 258 | /* Solaris sometimes sets additional bits in the mask */ |
a257cdd0 AG |
259 | entry->e_perm &= S_IRWXO; |
260 | break; | |
261 | default: | |
262 | return -EINVAL; | |
263 | } | |
264 | ||
265 | return 0; | |
266 | } | |
267 | ||
268 | static int | |
269 | cmp_acl_entry(const void *x, const void *y) | |
270 | { | |
271 | const struct posix_acl_entry *a = x, *b = y; | |
272 | ||
273 | if (a->e_tag != b->e_tag) | |
274 | return a->e_tag - b->e_tag; | |
ddca4e17 EB |
275 | else if ((a->e_tag == ACL_USER) && uid_gt(a->e_uid, b->e_uid)) |
276 | return 1; | |
277 | else if ((a->e_tag == ACL_USER) && uid_lt(a->e_uid, b->e_uid)) | |
278 | return -1; | |
279 | else if ((a->e_tag == ACL_GROUP) && gid_gt(a->e_gid, b->e_gid)) | |
a257cdd0 | 280 | return 1; |
ddca4e17 | 281 | else if ((a->e_tag == ACL_GROUP) && gid_lt(a->e_gid, b->e_gid)) |
a257cdd0 AG |
282 | return -1; |
283 | else | |
284 | return 0; | |
285 | } | |
286 | ||
287 | /* | |
288 | * Convert from a Solaris ACL to a POSIX 1003.1e draft 17 ACL. | |
289 | */ | |
290 | static int | |
291 | posix_acl_from_nfsacl(struct posix_acl *acl) | |
292 | { | |
293 | struct posix_acl_entry *pa, *pe, | |
294 | *group_obj = NULL, *mask = NULL; | |
295 | ||
296 | if (!acl) | |
297 | return 0; | |
298 | ||
299 | sort(acl->a_entries, acl->a_count, sizeof(struct posix_acl_entry), | |
300 | cmp_acl_entry, NULL); | |
301 | ||
ddca4e17 | 302 | /* Find the ACL_GROUP_OBJ and ACL_MASK entries. */ |
a257cdd0 AG |
303 | FOREACH_ACL_ENTRY(pa, acl, pe) { |
304 | switch(pa->e_tag) { | |
305 | case ACL_USER_OBJ: | |
a257cdd0 AG |
306 | break; |
307 | case ACL_GROUP_OBJ: | |
a257cdd0 AG |
308 | group_obj = pa; |
309 | break; | |
310 | case ACL_MASK: | |
311 | mask = pa; | |
df561f66 | 312 | fallthrough; |
a257cdd0 | 313 | case ACL_OTHER: |
a257cdd0 AG |
314 | break; |
315 | } | |
316 | } | |
317 | if (acl->a_count == 4 && group_obj && mask && | |
318 | mask->e_perm == group_obj->e_perm) { | |
319 | /* remove bogus ACL_MASK entry */ | |
320 | memmove(mask, mask+1, (3 - (mask - acl->a_entries)) * | |
321 | sizeof(struct posix_acl_entry)); | |
322 | acl->a_count = 3; | |
323 | } | |
324 | return 0; | |
325 | } | |
326 | ||
731f3f48 CL |
327 | /** |
328 | * nfsacl_decode - Decode an NFSv3 ACL | |
329 | * | |
330 | * @buf: xdr_buf containing XDR'd ACL data to decode | |
331 | * @base: byte offset in xdr_buf where XDR'd ACL begins | |
332 | * @aclcnt: count of ACEs in decoded posix_acl | |
333 | * @pacl: buffer in which to place decoded posix_acl | |
334 | * | |
335 | * Returns the length of the decoded ACL in bytes, or a negative errno value. | |
336 | */ | |
337 | int nfsacl_decode(struct xdr_buf *buf, unsigned int base, unsigned int *aclcnt, | |
338 | struct posix_acl **pacl) | |
a257cdd0 AG |
339 | { |
340 | struct nfsacl_decode_desc nfsacl_desc = { | |
341 | .desc = { | |
342 | .elem_size = 12, | |
343 | .xcode = pacl ? xdr_nfsace_decode : NULL, | |
344 | }, | |
345 | }; | |
346 | u32 entries; | |
347 | int err; | |
348 | ||
349 | if (xdr_decode_word(buf, base, &entries) || | |
350 | entries > NFS_ACL_MAX_ENTRIES) | |
351 | return -EINVAL; | |
58fcb8df | 352 | nfsacl_desc.desc.array_maxlen = entries; |
a257cdd0 AG |
353 | err = xdr_decode_array2(buf, base + 4, &nfsacl_desc.desc); |
354 | if (err) | |
355 | return err; | |
356 | if (pacl) { | |
357 | if (entries != nfsacl_desc.desc.array_len || | |
358 | posix_acl_from_nfsacl(nfsacl_desc.acl) != 0) { | |
359 | posix_acl_release(nfsacl_desc.acl); | |
360 | return -EINVAL; | |
361 | } | |
362 | *pacl = nfsacl_desc.acl; | |
363 | } | |
364 | if (aclcnt) | |
365 | *aclcnt = entries; | |
366 | return 8 + nfsacl_desc.desc.elem_size * | |
367 | nfsacl_desc.desc.array_len; | |
368 | } | |
57b696fb | 369 | EXPORT_SYMBOL_GPL(nfsacl_decode); |
6bb844b4 CL |
370 | |
371 | /** | |
372 | * nfs_stream_decode_acl - Decode an NFSv3 ACL | |
373 | * | |
374 | * @xdr: an xdr_stream positioned at an encoded ACL | |
375 | * @aclcnt: OUT: count of ACEs in decoded posix_acl | |
376 | * @pacl: OUT: a dynamically-allocated buffer containing the decoded posix_acl | |
377 | * | |
378 | * Return values: | |
379 | * %false: The encoded ACL is not valid | |
380 | * %true: @pacl contains a decoded ACL, and @xdr is advanced | |
381 | * | |
382 | * On a successful return, caller must release *pacl using posix_acl_release(). | |
383 | */ | |
384 | bool nfs_stream_decode_acl(struct xdr_stream *xdr, unsigned int *aclcnt, | |
385 | struct posix_acl **pacl) | |
386 | { | |
387 | const size_t elem_size = XDR_UNIT * 3; | |
388 | struct nfsacl_decode_desc nfsacl_desc = { | |
389 | .desc = { | |
390 | .elem_size = elem_size, | |
391 | .xcode = pacl ? xdr_nfsace_decode : NULL, | |
392 | }, | |
393 | }; | |
394 | unsigned int base; | |
395 | u32 entries; | |
396 | ||
397 | if (xdr_stream_decode_u32(xdr, &entries) < 0) | |
398 | return false; | |
399 | if (entries > NFS_ACL_MAX_ENTRIES) | |
400 | return false; | |
401 | ||
402 | base = xdr_stream_pos(xdr); | |
403 | if (!xdr_inline_decode(xdr, XDR_UNIT + elem_size * entries)) | |
404 | return false; | |
405 | nfsacl_desc.desc.array_maxlen = entries; | |
406 | if (xdr_decode_array2(xdr->buf, base, &nfsacl_desc.desc)) | |
407 | return false; | |
408 | ||
409 | if (pacl) { | |
410 | if (entries != nfsacl_desc.desc.array_len || | |
411 | posix_acl_from_nfsacl(nfsacl_desc.acl) != 0) { | |
412 | posix_acl_release(nfsacl_desc.acl); | |
413 | return false; | |
414 | } | |
415 | *pacl = nfsacl_desc.acl; | |
416 | } | |
417 | if (aclcnt) | |
418 | *aclcnt = entries; | |
419 | return true; | |
420 | } | |
421 | EXPORT_SYMBOL_GPL(nfs_stream_decode_acl); |