1 /* $NetBSD: ext2fs_xattr.c,v 1.4 2016/08/23 06:40:54 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2016 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jaromir Dolecek. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: ext2fs_xattr.c,v 1.4 2016/08/23 06:40:54 christos Exp $"); 34 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/mount.h> 38 #include <sys/proc.h> 39 #include <sys/file.h> 40 #include <sys/buf.h> 41 #include <sys/vnode.h> 42 #include <sys/kernel.h> 43 #include <sys/kmem.h> 44 #include <sys/trace.h> 45 #include <sys/resourcevar.h> 46 #include <sys/kauth.h> 47 #include <sys/extattr.h> 48 49 #include <ufs/ufs/inode.h> 50 #include <ufs/ufs/ufsmount.h> 51 #include <ufs/ufs/ufs_extern.h> 52 53 #include <ufs/ext2fs/ext2fs.h> 54 #include <ufs/ext2fs/ext2fs_extern.h> 55 #include <ufs/ext2fs/ext2fs_xattr.h> 56 57 static const char * const xattr_prefix_index[] = { 58 "", 59 "user.", 60 "system.posix_acl_access", 61 "system.posix_acl_default", 62 "trusted.", 63 "", /* unused */ 64 "security", 65 "system.", 66 "system.richacl", 67 "c", 68 }; 69 70 static int 71 ext2fs_find_xattr(struct ext2fs_xattr_entry *e, uint8_t *start, uint8_t *end, 72 int attrnamespace, struct uio *uio, size_t *size, uint8_t name_index, 73 const char *name) 74 { 75 uint8_t *value; 76 int error; 77 size_t value_offs, value_len, len, old_len; 78 79 /* 80 * Individual entries follow the header. Each is aligned on 4-byte 81 * boundary. 82 */ 83 for(; !EXT2FS_XATTR_IS_LAST_ENTRY(e, end); e = EXT2FS_XATTR_NEXT(e)) { 84 /* 85 * Only EXT2FS_XATTR_PREFIX_USER is USER, anything else 86 * is considered SYSTEM. 87 */ 88 if ((attrnamespace == EXTATTR_NAMESPACE_USER 89 && e->e_name_index != EXT2FS_XATTR_PREFIX_USER) || 90 (attrnamespace == EXTATTR_NAMESPACE_SYSTEM 91 && e->e_name_index == EXT2FS_XATTR_PREFIX_USER)) { 92 continue; 93 } 94 95 if (e->e_name_index != name_index || 96 e->e_name_len != strlen(name) || 97 strncmp(e->e_name, name, e->e_name_len) != 0) 98 continue; 99 100 value_offs = fs2h32(e->e_value_offs); 101 value_len = fs2h32(e->e_value_size); 102 value = &start[value_offs]; 103 104 /* make sure the value offset are sane */ 105 if (&value[value_len] > end) 106 return EINVAL; 107 108 if (uio != NULL) { 109 /* 110 * Figure out maximum to transfer -- use buffer size 111 * and local data limit. 112 */ 113 len = MIN(uio->uio_resid, value_len); 114 old_len = uio->uio_resid; 115 uio->uio_resid = len; 116 117 uio->uio_resid = old_len - (len - uio->uio_resid); 118 119 error = uiomove(value, value_len, uio); 120 if (error) 121 return error; 122 } 123 124 /* full data size */ 125 *size += value_len; 126 127 goto found; 128 } 129 130 /* requested attribute not found */ 131 return ENODATA; 132 133 found: 134 return 0; 135 } 136 137 static int 138 ext2fs_get_inode_xattr(struct inode *ip, int attrnamespace, struct uio *uio, 139 size_t *size, uint8_t name_index, const char *name) 140 { 141 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 142 struct ext2fs_xattr_ibody_header *h; 143 uint8_t *start, *end; 144 145 start = &((uint8_t *)di)[EXT2_REV0_DINODE_SIZE + di->e2di_extra_isize]; 146 h = (struct ext2fs_xattr_ibody_header *)start; 147 end = &((uint8_t *)di)[EXT2_DINODE_SIZE(ip->i_e2fs)]; 148 149 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 150 return ENODATA; 151 152 return ext2fs_find_xattr(EXT2FS_XATTR_IFIRST(h), start, end, 153 attrnamespace, uio, size, name_index, name); 154 } 155 156 static int 157 ext2fs_get_block_xattr(struct inode *ip, int attrnamespace, struct uio *uio, 158 size_t *size, uint8_t name_index, const char *name) 159 { 160 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 161 uint8_t *start, *end; 162 struct ext2fs_xattr_header *h; 163 int error = 0; 164 struct buf *bp = NULL; 165 daddr_t xblk; 166 167 xblk = di->e2di_facl; 168 if (EXT2F_HAS_INCOMPAT_FEATURE(ip->i_e2fs, EXT2F_INCOMPAT_64BIT)) 169 xblk |= (((daddr_t)di->e2di_facl_high) << 32); 170 171 /* don't do anything if no attr block was allocated */ 172 if (xblk == 0) 173 return 0; 174 175 error = bread(ip->i_devvp, fsbtodb(ip->i_e2fs, xblk), 176 (int)ip->i_e2fs->e2fs_bsize, 0, &bp); 177 if (error) 178 goto out; 179 180 start = (uint8_t *)bp->b_data; 181 h = (struct ext2fs_xattr_header *)start; 182 end = &((uint8_t *)bp->b_data)[bp->b_bcount]; 183 184 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 185 goto out; 186 187 error = ext2fs_find_xattr(EXT2FS_XATTR_BFIRST(h), start, end, 188 attrnamespace, uio, size, name_index, name); 189 190 out: 191 if (bp) 192 brelse(bp, 0); 193 return error; 194 } 195 int 196 ext2fs_getextattr(void *v) 197 { 198 struct vop_getextattr_args /* { 199 const struct vnodeop_desc *a_desc; 200 struct vnode *a_vp; 201 int a_attrnamespace; 202 const char *a_name; 203 struct uio *a_uio; 204 size_t *a_size; 205 kauth_cred_t a_cred; 206 } */ *ap = v; 207 struct inode *ip = VTOI(ap->a_vp); 208 char namebuf[EXT2FS_XATTR_NAME_LEN_MAX + 1]; 209 int error; 210 const char *prefix, *name; 211 uint8_t name_index; 212 size_t name_match, valuesize = 0; 213 214 if (ap->a_attrnamespace == EXTATTR_NAMESPACE_USER) 215 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_USER]; 216 else 217 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_SYSTEM]; 218 snprintf(namebuf, sizeof(namebuf), "%s%s", prefix, ap->a_name); 219 220 error = extattr_check_cred(ap->a_vp, namebuf, ap->a_cred, VREAD); 221 if (error) 222 return error; 223 224 /* 225 * Allow only offsets of zero to encourage the read/replace 226 * extended attribute semantic. Otherwise we can't guarantee 227 * atomicity, as we don't provide locks for extended attributes. 228 */ 229 if (ap->a_uio != NULL && ap->a_uio->uio_offset != 0) 230 return ENXIO; 231 232 /* figure out the name index */ 233 name = ap->a_name; 234 name_index = 0; 235 name_match = 0; 236 for(size_t i = 0; i < __arraycount(xattr_prefix_index); i++) { 237 prefix = xattr_prefix_index[i]; 238 size_t l = strlen(prefix); 239 if (l > 0 && strncmp(ap->a_name, prefix, l) == 0 && 240 name_match < l) { 241 name = &ap->a_name[l]; 242 name_index = i; 243 name_match = l; 244 continue; 245 } 246 } 247 248 /* fetch the xattr */ 249 error = ext2fs_get_inode_xattr(ip, ap->a_attrnamespace, ap->a_uio, 250 &valuesize, name_index, name); 251 if (error == ENODATA) { 252 /* not found in inode, try facl */ 253 error = ext2fs_get_block_xattr(ip, ap->a_attrnamespace, 254 ap->a_uio, &valuesize, name_index, name); 255 } 256 257 if (ap->a_size != NULL) 258 *ap->a_size = valuesize; 259 260 return error; 261 } 262 263 int 264 ext2fs_setextattr(void *v) 265 { 266 #if 0 267 struct vop_setextattr_args /* { 268 const struct vnodeop_desc *a_desc; 269 struct vnode *a_vp; 270 int a_attrnamespace; 271 const char *a_name; 272 struct uio *a_uio; 273 kauth_cred_t a_cred; 274 } */ *ap = v; 275 276 /* XXX set EXT2F_COMPAT_EXTATTR in superblock after successful set */ 277 #endif 278 279 /* XXX Not implemented */ 280 return EOPNOTSUPP; 281 } 282 283 static int 284 ext2fs_list_xattr(struct ext2fs_xattr_entry *e, uint8_t *end, 285 int attrnamespace, int flags, struct uio *uio, size_t *size) 286 { 287 char name[EXT2FS_XATTR_NAME_LEN_MAX + 1]; 288 uint8_t len; 289 int error; 290 const char *prefix; 291 292 /* 293 * Individual entries follow the header. Each is aligned on 4-byte 294 * boundary. 295 */ 296 for(; !EXT2FS_XATTR_IS_LAST_ENTRY(e, end); e = EXT2FS_XATTR_NEXT(e)) { 297 /* 298 * Only EXT2FS_XATTR_PREFIX_USER is USER, anything else 299 * is considered SYSTEM. 300 */ 301 if ((attrnamespace == EXTATTR_NAMESPACE_USER 302 && e->e_name_index != EXT2FS_XATTR_PREFIX_USER) || 303 (attrnamespace == EXTATTR_NAMESPACE_SYSTEM 304 && e->e_name_index == EXT2FS_XATTR_PREFIX_USER)) { 305 continue; 306 } 307 308 if (e->e_name_index < __arraycount(xattr_prefix_index)) 309 prefix = xattr_prefix_index[e->e_name_index]; 310 else 311 prefix = ""; 312 313 len = snprintf(name, sizeof(name), "%s%.*s", 314 prefix, 315 e->e_name_len, e->e_name); 316 317 if (uio != NULL) { 318 if (flags & EXTATTR_LIST_LENPREFIX) { 319 /* write name length */ 320 error = uiomove(&len, sizeof(uint8_t), uio); 321 if (error) 322 return error; 323 } else { 324 /* include trailing NUL */ 325 len++; 326 } 327 328 error = uiomove(name, len, uio); 329 if (error) 330 return error; 331 332 *size += len; 333 } 334 } 335 336 return 0; 337 } 338 339 static int 340 ext2fs_list_inode_xattr(struct inode *ip, int attrnamespace, int flags, 341 struct uio *uio, size_t *size) 342 { 343 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 344 void *start, *end; 345 struct ext2fs_xattr_ibody_header *h; 346 347 start = &((uint8_t *)di)[EXT2_REV0_DINODE_SIZE + di->e2di_extra_isize]; 348 h = start; 349 end = &((uint8_t *)di)[EXT2_DINODE_SIZE(ip->i_e2fs)]; 350 351 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 352 return 0; 353 354 return ext2fs_list_xattr(EXT2FS_XATTR_IFIRST(h), end, attrnamespace, 355 flags, uio, size); 356 } 357 358 static int 359 ext2fs_list_block_xattr(struct inode *ip, int attrnamespace, int flags, 360 struct uio *uio, size_t *size) 361 { 362 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 363 void *end; 364 struct ext2fs_xattr_header *h; 365 int error = 0; 366 struct buf *bp = NULL; 367 daddr_t xblk; 368 369 xblk = di->e2di_facl; 370 if (EXT2F_HAS_INCOMPAT_FEATURE(ip->i_e2fs, EXT2F_INCOMPAT_64BIT)) 371 xblk |= (((daddr_t)di->e2di_facl_high) << 32); 372 373 /* don't do anything if no attr block was allocated */ 374 if (xblk == 0) 375 return 0; 376 377 error = bread(ip->i_devvp, fsbtodb(ip->i_e2fs, xblk), 378 (int)ip->i_e2fs->e2fs_bsize, 0, &bp); 379 if (error) 380 goto out; 381 382 h = (struct ext2fs_xattr_header *)bp->b_data; 383 end = &((uint8_t *)bp->b_data)[bp->b_bcount]; 384 385 if (end <= (void *)h || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 386 goto out; 387 388 error = ext2fs_list_xattr(EXT2FS_XATTR_BFIRST(h), end, attrnamespace, 389 flags, uio, size); 390 391 out: 392 if (bp) 393 brelse(bp, 0); 394 return error; 395 } 396 397 int 398 ext2fs_listextattr(void *v) 399 { 400 struct vop_listextattr_args /* { 401 const struct vnodeop_desc *a_desc; 402 struct vnode *a_vp; 403 int a_attrnamespace; 404 struct uio *a_uio; 405 size_t *a_size; 406 int a_flag; 407 kauth_cred_t a_cred; 408 } */ *ap = v; 409 struct inode *ip = VTOI(ap->a_vp); 410 int error; 411 const char *prefix; 412 size_t listsize = 0; 413 414 if (!EXT2F_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_EXTATTR)) { 415 /* no EA on the filesystem */ 416 goto out; 417 } 418 419 /* 420 * XXX: We can move this inside the loop and iterate on individual 421 * attributes. 422 */ 423 if (ap->a_attrnamespace == EXTATTR_NAMESPACE_USER) 424 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_USER]; 425 else 426 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_SYSTEM]; 427 error = extattr_check_cred(ap->a_vp, prefix, ap->a_cred, VREAD); 428 if (error) 429 return error; 430 431 /* 432 * Allow only offsets of zero to encourage the read/replace 433 * extended attribute semantic. Otherwise we can't guarantee 434 * atomicity, as we don't provide locks for extended attributes. 435 * XXX revisit - vnode lock enough? 436 */ 437 if (ap->a_uio != NULL && ap->a_uio->uio_offset != 0) 438 return ENXIO; 439 440 /* fetch inode xattrs */ 441 error = ext2fs_list_inode_xattr(ip, ap->a_attrnamespace, ap->a_flag, 442 ap->a_uio, &listsize); 443 if (error) 444 return error; 445 446 error = ext2fs_list_block_xattr(ip, ap->a_attrnamespace, ap->a_flag, 447 ap->a_uio, &listsize); 448 if (error) 449 return error; 450 451 out: 452 if (ap->a_size != NULL) 453 *ap->a_size = listsize; 454 455 return 0; 456 } 457 458 int 459 ext2fs_deleteextattr(void *v) 460 { 461 #if 0 462 struct vop_deleteextattr_args /* { 463 const struct vnodeop_desc *a_desc; 464 struct vnode *a_vp; 465 int a_attrnamespace; 466 const char *a_name; 467 kauth_cred_t a_cred; 468 } */ *ap = v; 469 #endif 470 471 /* XXX Not implemented */ 472 return EOPNOTSUPP; 473 } 474