xref: /freebsd/sys/compat/linux/linux_xattr.c (revision d15f2551b25f79ddcbe289faa95e655100b952da)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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