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