1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023 Dmitry Chagin <dchagin@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/param.h>
29 #include <sys/capsicum.h>
30 #include <sys/extattr.h>
31 #include <sys/fcntl.h>
32 #include <sys/file.h>
33 #include <sys/namei.h>
34 #include <sys/proc.h>
35 #include <sys/syscallsubr.h>
36 #include <sys/vnode.h>
37
38 #include <security/mac/mac_framework.h>
39
40 #ifdef COMPAT_LINUX32
41 #include <machine/../linux32/linux.h>
42 #include <machine/../linux32/linux32_proto.h>
43 #else
44 #include <machine/../linux/linux.h>
45 #include <machine/../linux/linux_proto.h>
46 #endif
47
48 #include <compat/linux/linux_util.h>
49
50 #define LINUX_XATTR_SIZE_MAX 65536
51 #define LINUX_XATTR_LIST_MAX 65536
52 #define LINUX_XATTR_NAME_MAX 255
53
54 #define LINUX_XATTR_CREATE 0x1
55 #define LINUX_XATTR_REPLACE 0x2
56 #define LINUX_XATTR_FLAGS (LINUX_XATTR_CREATE | LINUX_XATTR_REPLACE)
57
58 struct listxattr_args {
59 int fd;
60 const char *path;
61 char *list;
62 l_size_t size;
63 int follow;
64 };
65
66 struct setxattr_args {
67 int fd;
68 const char *path;
69 const char *name;
70 void *value;
71 l_size_t size;
72 l_int flags;
73 int follow;
74 };
75
76 struct getxattr_args {
77 int fd;
78 const char *path;
79 const char *name;
80 void *value;
81 l_size_t size;
82 int follow;
83 };
84
85 struct removexattr_args {
86 int fd;
87 const char *path;
88 const char *name;
89 int follow;
90 };
91
92 static char *extattr_namespace_names[] = EXTATTR_NAMESPACE_NAMES;
93
94
95 static int
error_to_xattrerror(int attrnamespace,int error)96 error_to_xattrerror(int attrnamespace, int error)
97 {
98
99 if (attrnamespace == EXTATTR_NAMESPACE_SYSTEM && error == EPERM)
100 return (ENOTSUP);
101 else
102 return (error);
103 }
104
105 static int
xattr_to_extattr(const char * uattrname,int * attrnamespace,char * attrname)106 xattr_to_extattr(const char *uattrname, int *attrnamespace, char *attrname)
107 {
108 char uname[LINUX_XATTR_NAME_MAX + 1], *dot;
109 size_t len, cplen;
110 int error;
111
112 error = copyinstr(uattrname, uname, sizeof(uname), &cplen);
113 if (error != 0)
114 return (error);
115 if (cplen == sizeof(uname))
116 return (ERANGE);
117 dot = strchr(uname, '.');
118 if (dot == NULL)
119 return (ENOTSUP);
120 *dot = '\0';
121 for (*attrnamespace = EXTATTR_NAMESPACE_USER;
122 *attrnamespace < nitems(extattr_namespace_names);
123 (*attrnamespace)++) {
124 if (bcmp(uname, extattr_namespace_names[*attrnamespace],
125 dot - uname + 1) == 0) {
126 dot++;
127 len = strlen(dot) + 1;
128 bcopy(dot, attrname, len);
129 return (0);
130 }
131 }
132 return (ENOTSUP);
133 }
134
135 static int
listxattr(struct thread * td,struct listxattr_args * args)136 listxattr(struct thread *td, struct listxattr_args *args)
137 {
138 char attrname[LINUX_XATTR_NAME_MAX + 1];
139 char *data, *prefix, *key;
140 cap_rights_t rights;
141 struct file *fp = NULL;
142 struct uio auio;
143 struct iovec aiov;
144 unsigned char keylen;
145 size_t sz, cnt, rs, prefixlen, pairlen;
146 int attrnamespace, error;
147
148 if (args->path == NULL) {
149 error = getvnode(td, args->fd,
150 cap_rights_init_one(&rights, CAP_EXTATTR_LIST), &fp);
151 if (error != 0)
152 return (error);
153 }
154
155 if (args->size != 0)
156 sz = min(LINUX_XATTR_LIST_MAX, args->size);
157 else
158 sz = LINUX_XATTR_LIST_MAX;
159
160 data = malloc(sz, M_LINUX, M_WAITOK);
161 auio.uio_iov = &aiov;
162 auio.uio_iovcnt = 1;
163 auio.uio_rw = UIO_READ;
164 auio.uio_segflg = UIO_SYSSPACE;
165 auio.uio_td = td;
166 cnt = 0;
167 for (attrnamespace = EXTATTR_NAMESPACE_USER;
168 attrnamespace < nitems(extattr_namespace_names);
169 attrnamespace++) {
170 aiov.iov_base = data;
171 aiov.iov_len = sz;
172 auio.uio_resid = sz;
173 auio.uio_offset = 0;
174
175 if (args->path != NULL)
176 error = kern_extattr_list_path(td, args->path,
177 attrnamespace, &auio, args->follow, UIO_USERSPACE);
178 else
179 error = kern_extattr_list_fp(td, fp,
180 attrnamespace, &auio);
181 rs = sz - auio.uio_resid;
182 if (error == EPERM)
183 break;
184 if (error != 0 || rs == 0)
185 continue;
186 prefix = extattr_namespace_names[attrnamespace];
187 prefixlen = strlen(prefix);
188 key = data;
189 while (rs > 0) {
190 keylen = (unsigned char)key[0];
191 pairlen = prefixlen + 1 + keylen + 1;
192 cnt += pairlen;
193 if (cnt > LINUX_XATTR_LIST_MAX) {
194 error = E2BIG;
195 break;
196 }
197 /*
198 * If size is specified as zero, return the current size
199 * of the list of extended attribute names.
200 */
201 if ((args->size > 0 && cnt > args->size) ||
202 pairlen >= sizeof(attrname)) {
203 error = ERANGE;
204 break;
205 }
206 ++key;
207 if (args->list != NULL && args->size > 0) {
208 sprintf(attrname, "%s.%.*s", prefix, keylen, key);
209 error = copyout(attrname, args->list, pairlen);
210 if (error != 0)
211 break;
212 args->list += pairlen;
213 }
214 key += keylen;
215 rs -= (keylen + 1);
216 }
217 }
218 if (error == 0)
219 td->td_retval[0] = cnt;
220 free(data, M_LINUX);
221 if (fp != NULL)
222 fdrop(fp, td);
223 return (error_to_xattrerror(attrnamespace, error));
224 }
225
226 int
linux_listxattr(struct thread * td,struct linux_listxattr_args * args)227 linux_listxattr(struct thread *td, struct linux_listxattr_args *args)
228 {
229 struct listxattr_args eargs = {
230 .fd = -1,
231 .path = args->path,
232 .list = args->list,
233 .size = args->size,
234 .follow = FOLLOW,
235 };
236
237 return (listxattr(td, &eargs));
238 }
239
240 int
linux_llistxattr(struct thread * td,struct linux_llistxattr_args * args)241 linux_llistxattr(struct thread *td, struct linux_llistxattr_args *args)
242 {
243 struct listxattr_args eargs = {
244 .fd = -1,
245 .path = args->path,
246 .list = args->list,
247 .size = args->size,
248 .follow = NOFOLLOW,
249 };
250
251 return (listxattr(td, &eargs));
252 }
253
254 int
linux_flistxattr(struct thread * td,struct linux_flistxattr_args * args)255 linux_flistxattr(struct thread *td, struct linux_flistxattr_args *args)
256 {
257 struct listxattr_args eargs = {
258 .fd = args->fd,
259 .path = NULL,
260 .list = args->list,
261 .size = args->size,
262 .follow = 0,
263 };
264
265 return (listxattr(td, &eargs));
266 }
267
268 static int
removexattr(struct thread * td,struct removexattr_args * args)269 removexattr(struct thread *td, struct removexattr_args *args)
270 {
271 char attrname[LINUX_XATTR_NAME_MAX + 1];
272 struct file *fp = NULL;
273 cap_rights_t rights;
274 int attrnamespace, error;
275
276 if (args->path == NULL) {
277 error = getvnode(td, args->fd,
278 cap_rights_init_one(&rights, CAP_EXTATTR_DELETE), &fp);
279 if (error != 0)
280 return (error);
281 }
282
283 error = xattr_to_extattr(args->name, &attrnamespace, attrname);
284 if (error != 0)
285 goto out_err;
286 if (args->path != NULL)
287 error = kern_extattr_delete_path(td, args->path, attrnamespace,
288 attrname, args->follow, UIO_USERSPACE);
289 else
290 error = kern_extattr_delete_fp(td, fp, attrnamespace,
291 attrname);
292 if (fp != NULL)
293 fdrop(fp, td);
294 return (error_to_xattrerror(attrnamespace, error));
295 out_err:
296 if (fp != NULL)
297 fdrop(fp, td);
298 return (error);
299 }
300
301 int
linux_removexattr(struct thread * td,struct linux_removexattr_args * args)302 linux_removexattr(struct thread *td, struct linux_removexattr_args *args)
303 {
304 struct removexattr_args eargs = {
305 .fd = -1,
306 .path = args->path,
307 .name = args->name,
308 .follow = FOLLOW,
309 };
310
311 return (removexattr(td, &eargs));
312 }
313
314 int
linux_lremovexattr(struct thread * td,struct linux_lremovexattr_args * args)315 linux_lremovexattr(struct thread *td, struct linux_lremovexattr_args *args)
316 {
317 struct removexattr_args eargs = {
318 .fd = -1,
319 .path = args->path,
320 .name = args->name,
321 .follow = NOFOLLOW,
322 };
323
324 return (removexattr(td, &eargs));
325 }
326
327 int
linux_fremovexattr(struct thread * td,struct linux_fremovexattr_args * args)328 linux_fremovexattr(struct thread *td, struct linux_fremovexattr_args *args)
329 {
330 struct removexattr_args eargs = {
331 .fd = args->fd,
332 .path = NULL,
333 .name = args->name,
334 .follow = 0,
335 };
336
337 return (removexattr(td, &eargs));
338 }
339
340 /*-
341 * Linux-specific atomic extended attribute get on a vnode.
342 *
343 * Probes the attribute size and reads the data under a single vnode lock,
344 * preventing a TOCTOU race and returning ERANGE when the buffer is too
345 * small (matching Linux getxattr(2) semantics).
346 */
347 static int
linux_extattr_get_vp(struct vnode * vp,int attrnamespace,const char * attrname,void * data,size_t nbytes,struct thread * td)348 linux_extattr_get_vp(struct vnode *vp, int attrnamespace, const char *attrname,
349 void *data, size_t nbytes, struct thread *td)
350 {
351 struct uio auio;
352 struct iovec aiov;
353 size_t size;
354 int error;
355
356 if (nbytes > IOSIZE_MAX)
357 return (EINVAL);
358
359 vn_lock(vp, LK_SHARED | LK_RETRY);
360
361 #ifdef MAC
362 error = mac_vnode_check_getextattr(td->td_ucred, vp, attrnamespace,
363 attrname);
364 if (error != 0)
365 goto done;
366 #endif
367
368 /*
369 * Probe the attribute size first under the vnode lock;
370 */
371 error = VOP_GETEXTATTR(vp, attrnamespace, attrname, NULL,
372 &size, td->td_ucred, td);
373 if (error != 0)
374 goto done;
375
376 /*
377 * The caller only wants the size, so we are done after this.
378 */
379 if (data == NULL || nbytes == 0) {
380 td->td_retval[0] = size;
381 goto done;
382 }
383 /*
384 * If the buffer is too small, return ERANGE
385 * so the caller can retry (Linux getxattr semantics).
386 */
387 if (size > nbytes) {
388 error = ERANGE;
389 goto done;
390 }
391 /* Buffer is large enough; read the value. */
392 aiov.iov_base = data;
393 aiov.iov_len = nbytes;
394 auio.uio_iov = &aiov;
395 auio.uio_iovcnt = 1;
396 auio.uio_offset = 0;
397 auio.uio_resid = nbytes;
398 auio.uio_rw = UIO_READ;
399 auio.uio_segflg = UIO_USERSPACE;
400 auio.uio_td = td;
401 error = VOP_GETEXTATTR(vp, attrnamespace, attrname, &auio, NULL,
402 td->td_ucred, td);
403 if (error == 0)
404 td->td_retval[0] = nbytes - auio.uio_resid;
405 done:
406 VOP_UNLOCK(vp);
407 return (error);
408 }
409
410 static int
getxattr(struct thread * td,struct getxattr_args * args)411 getxattr(struct thread *td, struct getxattr_args *args)
412 {
413 char attrname[LINUX_XATTR_NAME_MAX + 1];
414 struct file *fp = NULL;
415 struct nameidata nd;
416 struct vnode *vp;
417 cap_rights_t rights;
418 int attrnamespace, error;
419
420 if (args->path == NULL) {
421 error = getvnode(td, args->fd,
422 cap_rights_init_one(&rights, CAP_EXTATTR_GET), &fp);
423 if (error != 0)
424 return (error);
425 vp = fp->f_vnode;
426 } else {
427 NDINIT_ATRIGHTS(&nd, LOOKUP, args->follow, UIO_USERSPACE,
428 args->path, AT_FDCWD,
429 cap_rights_init_one(&rights, CAP_EXTATTR_GET));
430 error = namei(&nd);
431 if (error != 0)
432 return (error);
433 NDFREE_PNBUF(&nd);
434 vp = nd.ni_vp;
435 }
436
437 error = xattr_to_extattr(args->name, &attrnamespace, attrname);
438 if (error == 0) {
439 error = linux_extattr_get_vp(vp, attrnamespace, attrname,
440 args->value, args->size, td);
441 }
442
443 if (fp != NULL) {
444 fdrop(fp, td);
445 } else {
446 vrele(nd.ni_vp);
447 }
448 return (error == EPERM || error == EOPNOTSUPP ? ENOATTR : error);
449 }
450
451 int
linux_getxattr(struct thread * td,struct linux_getxattr_args * args)452 linux_getxattr(struct thread *td, struct linux_getxattr_args *args)
453 {
454 struct getxattr_args eargs = {
455 .fd = -1,
456 .path = args->path,
457 .name = args->name,
458 .value = args->value,
459 .size = args->size,
460 .follow = FOLLOW,
461 };
462
463 return (getxattr(td, &eargs));
464 }
465
466 int
linux_lgetxattr(struct thread * td,struct linux_lgetxattr_args * args)467 linux_lgetxattr(struct thread *td, struct linux_lgetxattr_args *args)
468 {
469 struct getxattr_args eargs = {
470 .fd = -1,
471 .path = args->path,
472 .name = args->name,
473 .value = args->value,
474 .size = args->size,
475 .follow = NOFOLLOW,
476 };
477
478 return (getxattr(td, &eargs));
479 }
480
481 int
linux_fgetxattr(struct thread * td,struct linux_fgetxattr_args * args)482 linux_fgetxattr(struct thread *td, struct linux_fgetxattr_args *args)
483 {
484 struct getxattr_args eargs = {
485 .fd = args->fd,
486 .path = NULL,
487 .name = args->name,
488 .value = args->value,
489 .size = args->size,
490 .follow = 0,
491 };
492
493 return (getxattr(td, &eargs));
494 }
495
496 static int
setxattr(struct thread * td,struct setxattr_args * args)497 setxattr(struct thread *td, struct setxattr_args *args)
498 {
499 char attrname[LINUX_XATTR_NAME_MAX + 1];
500 struct file *fp = NULL;
501 cap_rights_t rights;
502 int attrnamespace, error;
503
504 if (args->path == NULL) {
505 if ((args->flags & LINUX_XATTR_FLAGS) != 0)
506 cap_rights_init(&rights, CAP_EXTATTR_GET, CAP_EXTATTR_SET);
507 else
508 cap_rights_init_one(&rights, CAP_EXTATTR_SET);
509 error = getvnode(td, args->fd, &rights, &fp);
510 if (error != 0)
511 return (error);
512 }
513
514 if ((args->flags & ~LINUX_XATTR_FLAGS) != 0 ||
515 args->flags == LINUX_XATTR_FLAGS) {
516 error = EINVAL;
517 goto out_err;
518 }
519 error = xattr_to_extattr(args->name, &attrnamespace, attrname);
520 if (error != 0)
521 goto out_err;
522
523 if ((args->flags & LINUX_XATTR_FLAGS) != 0) {
524 if (args->path != NULL)
525 error = kern_extattr_get_path(td, args->path,
526 attrnamespace, attrname, NULL, args->size,
527 args->follow, UIO_USERSPACE);
528 else
529 error = kern_extattr_get_fp(td, fp,
530 attrnamespace, attrname, NULL, args->size);
531 if ((args->flags & LINUX_XATTR_CREATE) != 0) {
532 if (error == 0)
533 error = EEXIST;
534 else if (error == ENOATTR)
535 error = 0;
536 }
537 if (error != 0)
538 goto out;
539 }
540 if (args->path != NULL)
541 error = kern_extattr_set_path(td, args->path, attrnamespace,
542 attrname, args->value, args->size, args->follow,
543 UIO_USERSPACE);
544 else
545 error = kern_extattr_set_fp(td, fp, attrnamespace,
546 attrname, args->value, args->size);
547 out:
548 if (fp != NULL)
549 fdrop(fp, td);
550 td->td_retval[0] = 0;
551 return (error_to_xattrerror(attrnamespace, error));
552 out_err:
553 if (fp != NULL)
554 fdrop(fp, td);
555 return (error);
556 }
557
558 int
linux_setxattr(struct thread * td,struct linux_setxattr_args * args)559 linux_setxattr(struct thread *td, struct linux_setxattr_args *args)
560 {
561 struct setxattr_args eargs = {
562 .fd = -1,
563 .path = args->path,
564 .name = args->name,
565 .value = args->value,
566 .size = args->size,
567 .flags = args->flags,
568 .follow = FOLLOW,
569 };
570
571 return (setxattr(td, &eargs));
572 }
573
574 int
linux_lsetxattr(struct thread * td,struct linux_lsetxattr_args * args)575 linux_lsetxattr(struct thread *td, struct linux_lsetxattr_args *args)
576 {
577 struct setxattr_args eargs = {
578 .fd = -1,
579 .path = args->path,
580 .name = args->name,
581 .value = args->value,
582 .size = args->size,
583 .flags = args->flags,
584 .follow = NOFOLLOW,
585 };
586
587 return (setxattr(td, &eargs));
588 }
589
590 int
linux_fsetxattr(struct thread * td,struct linux_fsetxattr_args * args)591 linux_fsetxattr(struct thread *td, struct linux_fsetxattr_args *args)
592 {
593 struct setxattr_args eargs = {
594 .fd = args->fd,
595 .path = NULL,
596 .name = args->name,
597 .value = args->value,
598 .size = args->size,
599 .flags = args->flags,
600 .follow = 0,
601 };
602
603 return (setxattr(td, &eargs));
604 }
605